Skip to content

Commit 0dc9c5d

Browse files
committed
Added options
1 parent fc7dd2b commit 0dc9c5d

11 files changed

Lines changed: 95 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
## Changelog
22

3+
### 2.0.9
4+
5+
- Added **Enable Found Devices** option to enable/disable all device tracker entities
6+
- Added **Track New Devices** option to control auto-creation of entities for newly discovered devices
7+
- Fixed mypy type errors in scanner.py
8+
39
### 2.0.8
410

511
- Added ZeroTier/VPN Hosts option to probe specific IPs instead of network scanning

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ Alternatively:
8181
| **Consider Home** | 180 seconds | Time before marking device as not_home (10-1800) |
8282
| **ARP Timeout** | 1.0 seconds | Timeout for each ARP request (0.5-10) |
8383
| **Resolve Hostnames** | Enabled | Look up device hostnames via reverse DNS |
84+
| **Enable Found Devices** | Enabled | Enable newly found device tracker entities by default |
85+
| **Track New Devices** | Enabled | Automatically create entities for newly discovered devices |
8486
| **Specific Hosts** | Empty | Probe only these IPs (for ZeroTier/VPN networks) |
8587
| **Include IPs** | Empty | Only track these IP addresses (comma-separated) |
8688
| **Exclude IPs** | Empty | Skip tracking these IP addresses (comma-separated) |
@@ -105,7 +107,8 @@ After initial setup, you can modify settings via:
105107
> [!NOTE]
106108
> All device trackers are separate devices—they aren't linked to ARP-Scanner instance by design.
107109
> If you see devices linked to the ARP-Scanner in the UI, it's Home Assistant linking other known devices because of MAC address matches.
108-
> All found devices are disabled by default, so you need to enable them in the Home Assistant UI.
110+
> When **Enable Found Devices** is off, new device trackers are added but disabled by default—enable them in the Home Assistant UI.
111+
> When **Track New Devices** is off, only previously known devices are tracked—new discoveries are ignored.
109112
110113
## Migration from YAML
111114

custom_components/arpscan_tracker/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,17 +118,17 @@ async def async_update_data() -> dict[str, dict[str, Any]]:
118118

119119
# Filter devices based on include/exclude lists
120120
result: dict[str, dict[str, Any]] = {}
121-
_LOGGER.debug("Processing %d scanned devices (include_list=%s, exclude_list=%s)",
121+
_LOGGER.debug("Processing %d scanned devices (include_list=%s, exclude_list=%s)",
122122
len(devices), include_list, exclude_list)
123-
123+
124124
for device in devices:
125125
ip = device["ip"]
126126
mac = device["mac"]
127127

128128
# Apply include filter (if specified, only include listed IPs)
129129
if include_list:
130130
if ip not in include_list:
131-
_LOGGER.debug("Device %s (%s) not in include list %s, skipping",
131+
_LOGGER.debug("Device %s (%s) not in include list %s, skipping",
132132
ip, mac, include_list)
133133
continue
134134
else:
@@ -147,7 +147,7 @@ async def async_update_data() -> dict[str, dict[str, Any]]:
147147
ip, mac, device.get("hostname"), device.get("vendor"))
148148
result[mac] = device
149149

150-
_LOGGER.debug("ARP scan returned %d devices after filtering: %s",
150+
_LOGGER.debug("ARP scan returned %d devices after filtering: %s",
151151
len(result), list(result.keys()))
152152
return result
153153

custom_components/arpscan_tracker/config_flow.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
from .const import (
2020
CONF_CONSIDER_HOME,
21+
CONF_DEVICES_ENABLED,
2122
CONF_EXCLUDE,
2223
CONF_HOSTS,
2324
CONF_INCLUDE,
@@ -26,10 +27,13 @@
2627
CONF_RESOLVE_HOSTNAMES,
2728
CONF_SCAN_INTERVAL,
2829
CONF_TIMEOUT,
30+
CONF_TRACK_NEW_DEVICES,
2931
DEFAULT_CONSIDER_HOME,
32+
DEFAULT_DEVICES_ENABLED,
3033
DEFAULT_RESOLVE_HOSTNAMES,
3134
DEFAULT_SCAN_INTERVAL,
3235
DEFAULT_TIMEOUT,
36+
DEFAULT_TRACK_NEW_DEVICES,
3337
DOMAIN,
3438
)
3539
from .scanner import get_available_interfaces, get_default_interface, get_interface_network
@@ -140,6 +144,12 @@ async def async_step_user(self, user_input: dict[str, Any] | None = None) -> Con
140144
CONF_RESOLVE_HOSTNAMES: user_input.get(
141145
CONF_RESOLVE_HOSTNAMES, DEFAULT_RESOLVE_HOSTNAMES
142146
),
147+
CONF_DEVICES_ENABLED: user_input.get(
148+
CONF_DEVICES_ENABLED, DEFAULT_DEVICES_ENABLED
149+
),
150+
CONF_TRACK_NEW_DEVICES: user_input.get(
151+
CONF_TRACK_NEW_DEVICES, DEFAULT_TRACK_NEW_DEVICES
152+
),
143153
CONF_INCLUDE: include_list,
144154
CONF_EXCLUDE: exclude_list,
145155
CONF_HOSTS: hosts_list,
@@ -177,6 +187,8 @@ async def async_step_user(self, user_input: dict[str, Any] | None = None) -> Con
177187
vol.Coerce(float), vol.Range(min=0.5, max=10.0)
178188
),
179189
vol.Optional(CONF_RESOLVE_HOSTNAMES, default=DEFAULT_RESOLVE_HOSTNAMES): bool,
190+
vol.Optional(CONF_DEVICES_ENABLED, default=DEFAULT_DEVICES_ENABLED): bool,
191+
vol.Optional(CONF_TRACK_NEW_DEVICES, default=DEFAULT_TRACK_NEW_DEVICES): bool,
180192
vol.Optional(CONF_INCLUDE, default=""): str,
181193
vol.Optional(CONF_EXCLUDE, default=""): str,
182194
vol.Optional(CONF_HOSTS, default=""): str,
@@ -290,6 +302,12 @@ async def async_step_init(self, user_input: dict[str, Any] | None = None) -> Con
290302
CONF_RESOLVE_HOSTNAMES: user_input.get(
291303
CONF_RESOLVE_HOSTNAMES, DEFAULT_RESOLVE_HOSTNAMES
292304
),
305+
CONF_DEVICES_ENABLED: user_input.get(
306+
CONF_DEVICES_ENABLED, DEFAULT_DEVICES_ENABLED
307+
),
308+
CONF_TRACK_NEW_DEVICES: user_input.get(
309+
CONF_TRACK_NEW_DEVICES, DEFAULT_TRACK_NEW_DEVICES
310+
),
293311
CONF_INCLUDE: include_list,
294312
CONF_EXCLUDE: exclude_list,
295313
CONF_HOSTS: hosts_list,
@@ -325,6 +343,18 @@ async def async_step_init(self, user_input: dict[str, Any] | None = None) -> Con
325343
CONF_RESOLVE_HOSTNAMES, DEFAULT_RESOLVE_HOSTNAMES
326344
),
327345
): bool,
346+
vol.Required(
347+
CONF_DEVICES_ENABLED,
348+
default=self.config_entry.options.get(
349+
CONF_DEVICES_ENABLED, DEFAULT_DEVICES_ENABLED
350+
),
351+
): bool,
352+
vol.Required(
353+
CONF_TRACK_NEW_DEVICES,
354+
default=self.config_entry.options.get(
355+
CONF_TRACK_NEW_DEVICES, DEFAULT_TRACK_NEW_DEVICES
356+
),
357+
): bool,
328358
vol.Optional(
329359
CONF_INCLUDE,
330360
default=", ".join(current_include) if current_include else "",

custom_components/arpscan_tracker/const.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
CONF_TIMEOUT: Final = "timeout"
1515
CONF_RESOLVE_HOSTNAMES: Final = "resolve_hostnames"
1616
CONF_HOSTS: Final = "hosts"
17+
CONF_DEVICES_ENABLED: Final = "devices_enabled"
18+
CONF_TRACK_NEW_DEVICES: Final = "track_new_devices"
1719

1820
# Defaults
1921
DEFAULT_SCAN_INTERVAL: Final = 15 # seconds
@@ -22,6 +24,8 @@
2224
DEFAULT_INTERFACE: Final = None # Auto-detect
2325
DEFAULT_NETWORK: Final = None # Auto-detect from interface
2426
DEFAULT_RESOLVE_HOSTNAMES: Final = True
27+
DEFAULT_DEVICES_ENABLED: Final = True
28+
DEFAULT_TRACK_NEW_DEVICES: Final = True
2529

2630
# Platforms
2731
PLATFORMS: Final = ["device_tracker"]

custom_components/arpscan_tracker/device_tracker.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,13 @@
2323
ATTR_MAC,
2424
ATTR_VENDOR,
2525
CONF_CONSIDER_HOME,
26+
CONF_DEVICES_ENABLED,
2627
CONF_INTERFACE,
28+
CONF_TRACK_NEW_DEVICES,
2729
DATA_COORDINATOR,
2830
DEFAULT_CONSIDER_HOME,
31+
DEFAULT_DEVICES_ENABLED,
32+
DEFAULT_TRACK_NEW_DEVICES,
2933
DOMAIN,
3034
)
3135

@@ -46,6 +50,8 @@ async def async_setup_entry(
4650
if isinstance(consider_home, timedelta):
4751
consider_home = int(consider_home.total_seconds())
4852
interface = entry.data.get(CONF_INTERFACE, "unknown")
53+
devices_enabled = entry.options.get(CONF_DEVICES_ENABLED, DEFAULT_DEVICES_ENABLED)
54+
track_new_devices = entry.options.get(CONF_TRACK_NEW_DEVICES, DEFAULT_TRACK_NEW_DEVICES)
4955

5056
# Track which devices we've already created entities for
5157
tracked_macs: set[str] = set()
@@ -73,6 +79,16 @@ async def async_setup_entry(
7379
)
7480
continue
7581

82+
# Update enabled/disabled state in registry based on config option
83+
if devices_enabled and entity.disabled_by == er.RegistryEntryDisabler.INTEGRATION:
84+
ent_reg.async_update_entity(entity.entity_id, disabled_by=None)
85+
_LOGGER.info("Enabling device tracker %s (devices_enabled=True)", entity.entity_id)
86+
elif not devices_enabled and entity.disabled_by is None:
87+
ent_reg.async_update_entity(
88+
entity.entity_id, disabled_by=er.RegistryEntryDisabler.INTEGRATION
89+
)
90+
_LOGGER.info("Disabling device tracker %s (devices_enabled=False)", entity.entity_id)
91+
7692
if mac_formatted not in tracked_macs:
7793
tracked_macs.add(mac_formatted)
7894
restored_entities.append(
@@ -82,6 +98,7 @@ async def async_setup_entry(
8298
consider_home=consider_home,
8399
interface=interface,
84100
entry_id=entry.entry_id,
101+
devices_enabled=devices_enabled,
85102
restored_name=entity.original_name or entity.name,
86103
)
87104
)
@@ -100,6 +117,10 @@ async def async_setup_entry(
100117
@callback
101118
def async_add_new_entities() -> None:
102119
"""Add entities for newly discovered devices."""
120+
if not track_new_devices:
121+
_LOGGER.debug("Track new devices is disabled, skipping new entity creation")
122+
return
123+
103124
new_entities: list[ArpScanDeviceTracker] = []
104125

105126
_LOGGER.debug("Checking for new entities: coordinator has %d devices, %d already tracked",
@@ -115,6 +136,7 @@ def async_add_new_entities() -> None:
115136
consider_home=consider_home,
116137
interface=interface,
117138
entry_id=entry.entry_id,
139+
devices_enabled=devices_enabled,
118140
)
119141
)
120142
_LOGGER.info("Creating NEW device tracker entity for MAC %s (IP: %s, hostname: %s)",
@@ -144,6 +166,7 @@ def __init__(
144166
consider_home: int,
145167
interface: str,
146168
entry_id: str,
169+
devices_enabled: bool = True,
147170
restored_name: str | None = None,
148171
) -> None:
149172
"""Initialize the device tracker."""
@@ -153,6 +176,7 @@ def __init__(
153176
self._consider_home = consider_home
154177
self._interface = interface
155178
self._entry_id = entry_id
179+
self._attr_entity_registry_enabled_default = devices_enabled
156180
self._last_seen: datetime | None = dt_util.utcnow() - timedelta(days=365)
157181

158182
# Get initial device data

custom_components/arpscan_tracker/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@
1313
"scapy>=2.5.0",
1414
"ouilookup>=0.2.4"
1515
],
16-
"version": "2.1.2"
16+
"version": "2.1.3"
1717
}

custom_components/arpscan_tracker/scanner.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ def _get_interface_info(self, interface: str) -> tuple[str | None, str | None]:
208208
Tuple of (ip_address, mac_address) or (None, None) if not available
209209
"""
210210
try:
211-
from scapy.all import get_if_addr, get_if_hwaddr # type: ignore[attr-defined]
211+
from scapy.all import get_if_addr, get_if_hwaddr
212212

213213
ip_addr = get_if_addr(interface)
214214
mac_addr = get_if_hwaddr(interface)
@@ -223,7 +223,8 @@ def _get_interface_info(self, interface: str) -> tuple[str | None, str | None]:
223223

224224
network = IPv4Network(network_str)
225225
# Use the interface's actual IP (first host in network)
226-
ip_addr = str(next(network.hosts(), None))
226+
first_host = next(iter(network.hosts()), None)
227+
ip_addr = str(first_host) if first_host else "0.0.0.0"
227228

228229
return (
229230
ip_addr if ip_addr and ip_addr != "0.0.0.0" else None,

custom_components/arpscan_tracker/strings.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
"consider_home": "Consider Home",
1212
"timeout": "ARP Timeout",
1313
"resolve_hostnames": "Resolve Hostnames (DNS)",
14+
"devices_enabled": "Enable Found Devices",
15+
"track_new_devices": "Track New Devices",
1416
"include": "Include IPs",
1517
"exclude": "Exclude IPs",
1618
"hosts": "ZeroTier/VPN Hosts (IPs)"
@@ -22,6 +24,8 @@
2224
"consider_home": "How long to wait before marking a device as not home (10-1800 seconds)",
2325
"timeout": "Timeout for each ARP request (0.5-10 seconds)",
2426
"resolve_hostnames": "Look up device hostnames via reverse DNS lookup",
27+
"devices_enabled": "Enable newly found device tracker entities by default. When disabled, new entities are added but disabled in the entity registry.",
28+
"track_new_devices": "Automatically create device tracker entities for newly discovered devices. When disabled, only previously known devices are tracked.",
2529
"include": "Only track devices with these IP addresses (comma-separated list)",
2630
"exclude": "Skip tracking devices with these IP addresses (comma-separated list)",
2731
"hosts": "Probe only these IPs instead of scanning the network (for ZeroTier, WireGuard, Tailscale, etc.)"
@@ -47,6 +51,8 @@
4751
"consider_home": "Consider Home",
4852
"timeout": "ARP Timeout",
4953
"resolve_hostnames": "Resolve Hostnames (DNS)",
54+
"devices_enabled": "Enable Found Devices",
55+
"track_new_devices": "Track New Devices",
5056
"hosts": "ZeroTier/VPN Hosts (IPs)",
5157
"include": "Include IPs",
5258
"exclude": "Exclude IPs"
@@ -56,6 +62,8 @@
5662
"consider_home": "How long to wait before marking a device as not home (10-1800 seconds)",
5763
"timeout": "Timeout for each ARP request (0.5-10 seconds)",
5864
"resolve_hostnames": "Look up device hostnames via reverse DNS lookup (may slow down scans)",
65+
"devices_enabled": "Enable newly found device tracker entities by default. When disabled, new entities are added but disabled in the entity registry.",
66+
"track_new_devices": "Automatically create device tracker entities for newly discovered devices. When disabled, only previously known devices are tracked.",
5967
"hosts": "Probe only these IPs instead of scanning the network (for ZeroTier, WireGuard, Tailscale, etc.)",
6068
"include": "Only track devices with these IP addresses (comma-separated list)",
6169
"exclude": "Skip tracking devices with these IP addresses (comma-separated list)"

custom_components/arpscan_tracker/translations/en.json

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
"data": {
88
"interface": "Network Interface",
99
"network": "Network Range (CIDR)",
10-
"scan_interval": "Scan Interval (seconds)",
11-
"consider_home": "Consider Home (seconds)",
12-
"timeout": "ARP Timeout (seconds)",
10+
"scan_interval": "Scan Interval",
11+
"consider_home": "Consider Home",
12+
"timeout": "ARP Timeout",
1313
"resolve_hostnames": "Resolve Hostnames (DNS)",
14+
"devices_enabled": "Enable Found Devices",
15+
"track_new_devices": "Track New Devices",
1416
"include": "Include IPs",
1517
"exclude": "Exclude IPs",
1618
"hosts": "ZeroTier/VPN Hosts (IPs)"
@@ -22,6 +24,8 @@
2224
"consider_home": "How long to wait before marking a device as not home (10-1800 seconds)",
2325
"timeout": "Timeout for each ARP request (0.5-10 seconds)",
2426
"resolve_hostnames": "Look up device hostnames via reverse DNS lookup",
27+
"devices_enabled": "Enable newly found device tracker entities by default. When disabled, new entities are added but disabled in the entity registry.",
28+
"track_new_devices": "Automatically create device tracker entities for newly discovered devices. When disabled, only previously known devices are tracked.",
2529
"include": "Only track devices with these IP addresses (comma-separated list)",
2630
"exclude": "Skip tracking devices with these IP addresses (comma-separated list)",
2731
"hosts": "Probe only these IPs instead of scanning the network (for ZeroTier, WireGuard, Tailscale, etc.)"
@@ -47,6 +51,8 @@
4751
"consider_home": "Consider Home",
4852
"timeout": "ARP Timeout",
4953
"resolve_hostnames": "Resolve Hostnames (DNS)",
54+
"devices_enabled": "Enable Found Devices",
55+
"track_new_devices": "Track New Devices",
5056
"hosts": "ZeroTier/VPN Hosts (IPs)",
5157
"include": "Include IPs",
5258
"exclude": "Exclude IPs"
@@ -56,6 +62,8 @@
5662
"consider_home": "How long to wait before marking a device as not home (10-1800 seconds)",
5763
"timeout": "Timeout for each ARP request (0.5-10 seconds)",
5864
"resolve_hostnames": "Look up device hostnames via reverse DNS lookup (may slow down scans)",
65+
"devices_enabled": "Enable newly found device tracker entities by default. When disabled, new entities are added but disabled in the entity registry.",
66+
"track_new_devices": "Automatically create device tracker entities for newly discovered devices. When disabled, only previously known devices are tracked.",
5967
"hosts": "Probe only these IPs instead of scanning the network (for ZeroTier, WireGuard, Tailscale, etc.)",
6068
"include": "Only track devices with these IP addresses (comma-separated list)",
6169
"exclude": "Skip tracking devices with these IP addresses (comma-separated list)"

0 commit comments

Comments
 (0)