| Developer: | Karl's Plugins Like this plugin? Show your appreciation! |
| Category: | Location and Presence |
| Github: | Github Repo |
| License: | MIT License |
| Assistance: | Get help! |
| Plugin ID: | com.karlwachs.networkscanner |
| Latest release: | v2026.5.51 released on April 23, 2026 |
| Release downloaded: | 3 times |
| Requires: | Indigo v2022.1.0 or higher |
| (Check the Releases tab below for older releases that may have different requirements) | |
| Download release v2026.5.32 |
Beyond basic LAN scanning the plugin provides:
networkScanner_pingDevice variable.No third-party packages required.
/usr/sbin/tcpdump — Passive traffic sniffing, captures ARP, mDNS (port 5353) and DHCP (ports 67/68)/usr/sbin/arp — Reads ARP cache after sweep/sbin/ifconfig — Determines local subnet (IP and netmask)Python socket — ICMP ping (SOCK_DGRAM / IPPROTO_ICMP) and TCP-connect probe (SOCK_STREAM) — no subprocess, no root requiredMAC2Vendor.py — Bundled OUI vendor lookup, auto-downloads IEEE tables on first run, caches locallymacOS password required for tcpdump.
tcpdumpneeds elevated privileges to open the BPF network socket for passive sniffing. Enter your macOS login password in Plugins → Network Scanner → Configure… → sudo Password. Leave blank only if tcpdump already has the BPF entitlement granted viasudo chmodor a system policy.
Passive traffic sniffing — tcpdump listens for ARP, mDNS (port 5353) and DHCP (ports 67/68). Any matching packet updates the device's last-seen timestamp. Catches devices that suppress ARP (iOS privacy mode, VMs, IoT). Each MAC throttled to one Indigo update per 30 s. Requires sudo password if tcpdump does not already have the BPF entitlement.
Active ARP sweep — sends parallel ICMP pings to every host on the subnet, then reads the kernel ARP cache with arp -a. Only devices that actually respond to ping or TCP probe have their last-seen updated. Stale cache entries do not count as online. After processing, the plugin runs sudo arp -d -a to flush the ARP cache so stale entries from powered-off devices do not persist into the next cycle (requires sudo password). The arp -a output also captures the network interface (en0 = Wi-Fi, en1 = Ethernet) for each device, stored in networkInterface.
Periodic reachability probe — runs every scan-interval:
pingMs) and IP TTL.ConnectionRefusedError counts as alive.scan-interval − 10 s, the per-device probe is skipped — the sweep result is used directly, reducing redundant probing. pingOnly devices have their own adaptive timer (see below) and are handled separately: if the ARP sweep pinged the same IP within the last 10 s, the dedicated pingOnly probe is skipped for that cycle and the timer is advanced, preventing a double-ping.curlPort is set) but is failing now, the device stays offline — no second-ICMP fallback (which proxy-ARP can fake). Second ICMP is used only for devices where TCP has genuinely never worked (pure IoT with no open ports).last_seen via proxy-ARP replies even when the device is offline, preventing the offline threshold from expiring. pingOnly devices use a separate ping_only_last_ping_ok timestamp that is updated only by the dedicated ICMP probe — never by the ARP sweep. The offline threshold is measured against this timestamp, making it immune to proxy-ARP sweep noise.DHCP enrichment — the separate DHCP sniffer (tcpdump -vv on ports 67/68) extracts:
dhcpHostname. Populated only when a device sends a DHCP request — typically on first connect or lease renewal (can take hours for already-connected devices).dhcpOsFingerprint. The sequence of option numbers acts as an OS fingerprint: Windows {249,252} · Apple macOS/iOS [_, 121, ...] · Linux {28,2} · Android {33,26}. Same timing constraint as dhcpHostname.mDNS / Bonjour enrichment — dns-sd browses all advertised service types every 5 minutes and resolves TXT records:
md= field → mdnsModel (e.g. HomePod mini, BRAVIA XR)am= field → appleModel (Apple internal model code, e.g. AudioAccessory1,1)osxvers= field → osVersion (macOS/iOS kernel version)mdnsServices and → deviceType (derived via priority-ordered service map: _airplay._tcp = Smart Speaker / AV · _ipp._tcp = Printer · etc.)Note on enrichment state population:
dhcpHostname,dhcpOsFingerprintare only captured on DHCP events (device reconnect / lease renewal).mdnsModel,appleModel,osVersion,deviceTyperequire the device to advertise mDNS services. These states may remain empty for devices that are already connected with valid leases, or devices that simply do not support the relevant protocol.
Plugins → Network Scanner → Configure…
en0, eth0). Leave blank to auto-detect. (default: auto)00:00:00:00:00:XX. Leave off unless you need cross-VLAN monitoring — some routers answer ping for every subnet IP, flooding the plugin with ghost devices. (default: off)Net_* devices (auto-created). Leave blank for root. (default: Network Devices)Net_ → Net_AA:BB:CC:DD:EE:FF). (default: Net_)false on every managed Network Device in one click.plugin.log. Each message includes the source, e.g. "Device X is now ONLINE via sweep (arp)" or "is now OFFLINE [timeout]".arp -a during each sweep.Track Specific Device (MAC or IP) — enter one or more comma-separated MAC addresses (aa:bb:cc:dd:ee:ff) or IP addresses (192.168.1.5) in the debug section of the configuration dialog.
Use the ▶ Activate Tracking Now button to start tracing immediately — no need to save the dialog. Use ■ Stop Tracking to cancel. Tracking is always cleared automatically when the plugin restarts so it can never silently stay on.
While active, every event touching a listed device is written to plugin.log at DEBUG level with a [TRACE <target>] prefix:
raw-tcpdump — Complete raw tcpdump output line (before any parsing or throttle)sniff-ARP-reply — ARP reply parsed from tcpdumpsniff-frame — Non-ARP frame from which src MAC + IP were extractedarp-a — Entry parsed from arp -a during the sweep, with replied flag_register_device — Every call that updates _known and pushes to Indigo, with source, changed_ip, skip_pushping-recheck — Offline-to-online confirmation result (ICMP+TCP guard)_state_update — Every Indigo state write — online_changed, source, ip_changedValidation: the Activate button accepts only exact MAC format (
xx:xx:xx:xx:xx:xx) or valid IPv4. Invalid entries are rejected with a descriptive error shown in the dialog.
Net_*)One device per discovered MAC address. Auto-created when Auto-Create Devices is on.
192.168.1.005) for correct alphabetical sort by IP (or MAC when Flip is ON)Net_AA:BB:CC:DD:EE:FF, then automatically renamed to include vendor / local name once knownA manually configured host (IP address or DNS name) pinged on a fixed interval. No MAC tracking — useful for monitoring internet connectivity. Create via New Device → External Device or use Add Internet Ping Devices… in the plugin menu.
Aggregate device that watches up to 6 Network Devices and tracks presence.
ParticipantsHome — count of currently online participantsparticipants — comma-separated Indigo device IDs of all configured slots192.168.1. 112 22 44); otherwise full IPs are shown.192.168.1. - 12 15 25), updated live (or MACs when Flip is ON)Delay before OFF — optional grace period (0 / 10 / 20 / 30 / 60 / 90 / 120 / 180 s) before the device flips to OFF when all participants go offline. If any participant comes back online during the delay the OFF transition is cancelled and the device stays ON. Useful to absorb brief WiFi drops or proxy-ARP timing artefacts that would otherwise trigger Away automations prematurely. Default is 0 (immediate).
Use the ParticipantsHome state in Indigo conditions to check if at least N people are home.
Aggregate device that watches up to 3 External Devices and tracks internet connectivity.
ParticipantsOnline — count of currently reachable participantsparticipants — comma-separated Indigo device IDs of all configured slotswww. and TLD stripped, separated by · (e.g. google · yahoo · welt). Never overwritten.id,id,id - host,host,host; never overwrittenMonitors the public (WAN) IP address of this machine. Fetches from well-known IP-echo services on a configurable interval and stores the result in the publicIp state (shown as the device display state in the Indigo device list).
publicIp — shown as on 203.0.113.42 or off 203.0.113.42 so status and IP are visible at a glance. The state value (for triggers and scripts) is always the bare IP address only.Services tried in order (first to respond wins): api.ipify.org → checkip.amazonaws.com → icanhazip.com. Each has a 10-second timeout.
Create via Plugins → Network Scanner → Add Internet Address Device… (safe to click multiple times — skips if device already exists).
Double-click any Net_* device
The probe runs in addition to passive detection (ARP sweep + tcpdump). Passive detection always marks the device online immediately when traffic is seen; the probe adds a second layer of active confirmation.
Who wins — a failed ping vs a recent passive sighting?
timed_out = false, so a failed ping cannot take the device offline on its own.Ping success always wins regardless of mode — device goes online immediately.
Probe mode options:
Ping only uses a dedicated probe schedule that runs independently of the global scan interval (the scan loop checks pingOnly devices every 15 s so the adaptive timer fires on time regardless of the sweep period):
The offline threshold is measured against ping_only_last_ping_ok — a timestamp that is updated only when the dedicated probe itself succeeds. The ARP sweep may receive proxy-ARP replies for offline devices and would otherwise reset last_seen, preventing the threshold from ever expiring. ping_only_last_ping_ok is unaffected by sweep results.
This makes it suitable for devices that suppress ARP and mDNS (routers, cameras, printers) as well as devices added manually with a custom MAC where no passive traffic is expected. The Offline Trigger Logic and Missed Pings Before Offline settings are not used in this mode — the threshold alone controls the on→off transition.
Tip: Set Online Ping Interval to 30–50 % of the Offline Threshold so the device is confirmed online at least twice before it can time out. For example, with a 3-minute threshold, set the interval to 60–90 s.
When Offline Trigger Logic is set to OR and the first ping fails while the device is online, the plugin immediately sends 2 additional ICMP pings 3 s apart before accepting the failure — mirroring ping -c 3 -i 3. A single dropped packet is confirmed or dismissed within ~6 s rather than waiting for the next 15 s poll cycle.
Note: The 15 s / 60 s probe intervals and 2 × 3 s retry parameters are defined as named constants at the top of
plugin.py(_PING_ONLY_INTERVAL_ONLINE,_PING_ONLY_INTERVAL_OFFLINE,_PING_RETRY_COUNT,_PING_RETRY_INTERVAL) and can be adjusted there without touching any other logic.
0 = use the global Scan Interval. Smaller values catch recovery faster at the cost of more pings. Not used in Ping only mode (which has its own 15 s adaptive timer). (default: 0)0 = default (60 s for Ping only; global Scan Interval for other modes). Increase to reduce traffic for stable devices; decrease for faster re-confirmation. Recommended: set to 30–50 % of the Offline Threshold so the device is confirmed at least twice before timing out. Applies to all active ping modes. (default: 0)0 = use plugin-wide default. In Ping only mode this is the sole condition that triggers offline. (default: 0)ipNumber state and known_devices.json. Will be overwritten on the next scan if the device is seen with a different IP.comment state.plugin.log entry each time this device is seen. (default: off)onOffState (Boolean) — True = online, False = offlineipNumber (String) — Last seen IP addresspreviousIps (String) — Last 10 IP addresses used by this device with dates, e.g. 192.168.1.100 (2026-04-20) | 192.168.1.101 (2026-04-19). Updated each time the IP changes.MACNumber (String) — MAC addressmdnsName (String) — Hostname from mDNS / Bonjour SRV records (e.g. iPhone-Karl.local). Populated by the passive sniff thread and mDNS browse.arpHostname (String) — Hostname from arp -a output — the first field when macOS resolves a Bonjour name for the IP (e.g. iphone.localdomain). Updated each ARP sweep. Only set when a real name (not ?) is found.hardwareVendor (String) — Manufacturer from bundled OUI tabledhcpHostname (String) — Device hostname from DHCP option 12, populated on DHCP request/renewal (e.g. Karl-iPhone).dhcpOsFingerprint (String) — OS guess from DHCP option 55 parameter request list: Windows · Apple (macOS/iOS) · Linux · Android. Populated on DHCP events only.mdnsServices (String) — Comma-separated mDNS service types advertised by this device (e.g. _airplay._tcp, _raop._tcp). Cumulative — services are added but never removed.mdnsModel (String) — Device model string from mDNS TXT md= field (e.g. HomePod mini, BRAVIA XR).deviceType (String) — Device category derived from advertised mDNS services (e.g. Smart Speaker / AV · Printer · NAS / Server). Only set for devices that advertise mDNS.appleModel (String) — Apple internal model code from mDNS TXT am= field (e.g. AudioAccessory1,1, iPhone14,5). Apple devices only.osVersion (String) — macOS / iOS kernel version from mDNS TXT osxvers= field. Apple devices only.osHint (String) — Best-guess OS: Windows · Linux/macOS/iOS · Android · Cisco/Network. Derived from DHCP vendor class (option 60), DHCP option 55 fingerprint, or IP TTL as last resort.networkInterface (String) — Network interface the device was last seen on, from arp -a output (e.g. en0 = Wi-Fi, en1 = Ethernet).pingMs (String) — Last ICMP round-trip time in ms (e.g. 12 ms). Updated only when RTT changes by more than 40% and more than 20 ms.changeToOn (String) — Timestamp (YYYY-MM-DD HH:MM:SS) of the most recent offline → online transition.changeToOff (String) — Timestamp (YYYY-MM-DD HH:MM:SS) of the most recent online → offline transition.lastOnOffChange (String) — Timestamp of last online ↔ offline transition.created (String) — Timestamp when the Indigo device was first created.openPorts (String) — Comma-separated open TCP ports from last port scan.comment (String) — Free-text note set in device edit.isApOrRouter (Boolean) — True when the device is a proxy-ARP AP or router. Set automatically after 3+ IP changes within 10 minutes, or manually in device edit. When True, IP updates from passive sniffing are suppressed — only the ARP sweep can change the IP.pingMode (String) — Ping/probe mode synced from device settings (e.g. both, confirm, none).lastOnMessage (String) — Timestamp of the most recent online confirmation (YYYY-MM-DD HH:MM). Updated at most once per minute while the device is online.setOnBy (String) — What mechanism last set the device online: sweep (arp) · traffic observed (tcpdump) · ping(ICMP) · tcp:<port>.setOffBy (String) — What mechanism last set the device offline: timeout · probe.fingscanDeviceInfo (String) — Fing scan info imported via migration tool.onOffState (Boolean) — True = reachable, False = offlinehost (String) — Configured hostname or IPipNumber (String) — Resolved IP addresspingMs (String) — Last ping round-trip time (e.g. 12 ms) or timeoutlastOnOffChange (String) — Timestamp of last online ↔ offline transitioncomment (String) — Free-text noteApplies to both Network Devices — Home or Away and External Devices — Online / Offline.
onOffState (Boolean) — True = at least one participant is online/homeParticipantsHome / ParticipantsOnline (Integer) — Count of currently online/home participantsparticipants (String) — Comma-separated Indigo device IDs of all configured slotslastOnOffChange (String) — Timestamp of last on/off transitioncomment (String) — Free-text noteonOffState (Boolean) — True = last fetch succeeded, False = all services unreachablepublicIp (String) — Current public (WAN) IP address. State value = bare IP; displayed in the Indigo device list as on 203.0.113.42 / off 203.0.113.42.previousIp (String) — IP address before the most recent changelastChanged (String) — Timestamp when publicIp last changedlastSuccessfulUpdate (String) — Timestamp of the most recent successful fetch (YYYY-MM-DD HH:MM:SS)lastFailedUpdate (String) — Timestamp of the most recent failed fetch (YYYY-MM-DD HH:MM:SS)comment (String) — Free-text noteEach time a new MAC address is auto-created as an Indigo device, the variable networkScanner_newdevice is updated to {deviceId} {timestamp}.
How to use: Create an Indigo Trigger on Variable Changed → networkScanner_newdevice, then add a Run Script action:
info = indigo.variables["networkScanner_newdevice"].value
ipDevVarNumber = int(info.split(" ")[0])
dev = indigo.devices[ipDevVarNumber]
st = dev.states # shortcut
theSubject = "new device on network " + dev.name
theBody = "new device on network: " + dev.name + "\n"
theBody += "ipNumber: " + st["ipNumber"] + "\n"
theBody += "MACNumber: " + st["MACNumber"]+ "\n"
theBody += "hardwareVendor: " + st["hardwareVendor"]+ "\n"
theBody += "indigoID: " + str(dev.id) + "\n"
indigo.server.log(theBody)
indigo.server.sendEmailTo("your email address", subject=theSubject, body=theBody)
networkScanner_newdevice — Updated on every new Network Device auto-creation. Value: {deviceId} {timestamp}. Created at startup if it doesn't exist. Placed in the configured Variable Folder.networkScanner_pingDevice — Updated after every manual ping (menu item, action, or Add Internet Ping Devices). Value: {ip} {ms}ms on/off (e.g. 142.250.80.46 22ms on). Created at startup if it doesn't exist.Plugins → Network Scanner
networkScanner_pingDevice as {ip} {ms}ms on/off.Ping-{host}. Safe to run multiple times — skips any host that already exists. Also contains an Add Internet Address device button.++++ new ++++. Also runs automatically once per night after 02:00 in quiet mode — only devices with newly discovered ports are printed, plus no new ports found if nothing changed.openPorts state. Estimated run time: 8–17 minutes.osHint, osVersion, dhcpOsFingerprint, deviceType, networkInterface, and open port. Only populated buckets are shown.Available in Indigo Action Groups
plugin.log and to the variable networkScanner_pingDevice as {ip} {ms}ms on/off.Tracks the time between consecutive sightings of each device, bucketed into:
≤10s · ≤30s · ≤60s · ≤90s · ≤120s · ≤180s · ≤240s · ≤300s · >300s
Plugins → Network Scanner → Manage Ignored MAC Addresses…
Ignored MACs are neither created nor updated by the scanner.
Two separate states capture device hostnames from different sources:
mdnsName — from mDNS / Bonjour SRV records (passive sniff thread or dns-sd browse). Example: iPhone-Karl.local. More reliable; only updated when a DNS-SD packet is seen.arpHostname — from the first field of arp -a output, which macOS fills from its Bonjour cache. Example: iphone-karl.localdomain. Updated every ARP sweep, but only when a real name (not ?) is found.Both states are write-once-per-source — a ? result from arp -a never erases a previously discovered arpHostname. Devices that don't advertise Bonjour names have both states empty.
36 ports probed by the broad port scan and the slow port scan:
21 (FTP), 22 (SSH), 23 (Telnet), 25 (SMTP), 53 (DNS), 80 (HTTP), 110 (POP3), 123 (NTP†), 143 (IMAP), 161 (SNMP†), 162 (SNMP-trap†), 389 (LDAP), 443 (HTTPS), 445 (SMB), 548 (AFP), 554 (RTSP), 587 (SMTP submission), 631 (IPP/printing), 636 (LDAPS), 989 (FTPS-data), 990 (FTPS), 993 (IMAPS), 995 (POP3S), 1400 (Sonos), 1883 (MQTT), 3306 (MySQL), 3389 (RDP), 5000 (UPnP/dev server), 5432 (PostgreSQL), 5900 (VNC), 8080 (HTTP-alt), 8123 (Home Assistant), 8443 (HTTPS-alt), 9100 (Raw printing), 32400 (Plex), 32469 (Plex DLNA)
† NTP (123), SNMP (161), and SNMP-trap (162) are primarily UDP services. A TCP hit on these ports indicates the daemon also listens on TCP (NTP time transfer, SNMPv3 TCP transport).
known_devices.json is loaded at startup — previously discovered devices are immediately available.self.in_grace_period) is active from plugin start until the configured Ignore offline changes at startup duration expires. While the flag is True:_known.passive-info debug log is silenced — bulk state updates from the first sweep do not flood the log.False by runConcurrentThread (the single authoritative location) and is read directly everywhere else — no inline time calculations at the call sites.All discovered device data (IP, last-seen, vendor, local name, ping-fail streak, statistics, IP change history) is saved to:
<Indigo install>/Preferences/Plugins/com.karlwachs.networkscanner/known_devices.json
Saved after every scan cycle and on shutdown (including SIGTERM).
Each time a device's IP address changes, a record is appended to its ip_history list (capped at 20 entries):
ts — Timestamp of the changeold_ip — Previous IP addressnew_ip — New IP addresssource — scan (changed by ARP sweep / sniff) or manual (set via device edit)Visible in List All Discovered Devices and Print IP-Changed Devices menu items, where each line shows [scan] or [manual].
Four tools under Plugins → Network Scanner → Fingscan Migration Tools…:
IP-Device entries to NetworkScanner devices by MAC and writes the Fingscan name into the fingscanDeviceInfo state of each matched device.fingscanDeviceInfo values to rename each NetworkScanner device to {fingscan-name}-{prefix} (e.g. Karl iPhone-Net). Only renames devices where fingscanDeviceInfo is non-empty.networkDevice for each one:pingMode = "pingOnly", device starts disabled — enable each one manually to begin monitoring{fingscan-name}-{prefix}-ping-only (e.g. Karl iPhone-Net-ping-only)0.0.0.0, or a non-private (public/internet) IP address — add those as External Devices instead. Private ranges accepted: 10.x.x.x, 172.16–31.x.x, 192.168.x.xAuthor: Karl Wachs
| Released on: | April 23, 2026 |
| Requires: | Indigo v2022.1.0+ |
| Downloaded: | 3 times |
| Download this release | |
see changelog
| Released on: | April 23, 2026 |
| Requires: | Indigo v2022.1.0+ |
| Downloaded: | 4 times |
| Download this release | |
see change log
| Released on: | April 22, 2026 |
| Requires: | Indigo v2022.1.0+ |
| Downloaded: | 3 times |
| Download this release | |
see changelog
| Released on: | April 21, 2026 |
| Requires: | Indigo v2022.1.0+ |
| Downloaded: | 3 times |
| Download this release | |
see changelog for details
| Released on: | April 21, 2026 |
| Requires: | Indigo v2022.1.0+ |
| Downloaded: | 5 times |
| Download this release | |
see read.me and changlog
| Released on: | April 20, 2026 |
| Requires: | Indigo v2022.1.0+ |
| Downloaded: | 4 times |
| Download this release | |
first production release
| v2025.0.11 | Requires Indigo v2022.1.0+ | Released April 16, 2026 | fixed key fingscanDeviceName not found in dict |
| Released on: | April 16, 2026 |
| Requires: | Indigo v2022.1.0+ |
| Downloaded: | 5 times |
| Download this release | |
fixed key fingscanDeviceName not found in dict
| Released on: | April 16, 2026 |
| Requires: | Indigo v2022.1.0+ |
| Downloaded: | 4 times |
| Download this release | |
various changes see change log
| Released on: | April 15, 2026 |
| Requires: | Indigo v2022.1.0+ |
| Downloaded: | 6 times |
| Download this release | |
Network Scanner — discovers LAN devices via ARP sniffing, sweep and ping. Creates one Indigo device per MAC address; tracks online/offline state, IP, vendor and open ports
| v2025.0.2 | Requires Indigo v2022.1.0+ | Released April 15, 2026 | Discovers LAN devices via ARP sniffing, sweep and ping. Creates one Indigo device per MAC address; t |
| Released on: | April 15, 2026 |
| Requires: | Indigo v2022.1.0+ |
| Downloaded: | 5 times |
| Download this release | |
first release