DD-WRT Presence Detection with ARP for openHAB

For those of us letting our DD-WRT router handle presence detection we are likely aware of the following website:

This script modification/adaptation from ISY to openHAB was done by Aaron Tinsley (Thanks Aaron!).

Aaron’s script extracts MAC addresses from the ASSOCLIST at the AP level. While it is a good/working script. It has limited usefulness as it reviews connect/disconnect MAC details only for a specified Wi-Fi Radio. Also, the script does nothing to monitor hard wired LAN devices.

Given most current router hardware has 2 if not 3 radios these days, and a desire to monitor hard wired clients led me to the “arp” command. This command will monitor all of br0 MAC addresses i.e. dual band radios (both 2.4 GHz and 5GHz) as well as hard wired network clients.

Credit for the arp idea goes to user “scastano” on the universal devices forum (linked from Aaron’s website).

openHAB Items:

//DD-WRT Presence
String DDWRTDeviceCount “DD-WRT Devices Connected [%s]” //Total Number of Monitored Devices Connected
Switch JoePhone
Switch WifePhone
Switch SamsungTV
Switch KidIPAD

openHAB Sitemap Example:

Frame label=“DD-WRT Presence” {
//DD-WRT Switches
Text item=DDWRTDeviceCount {
Switch item=JoePhone
Switch item=WifePhone
Switch item=SamsungTV
Switch item=KidIPAD
}
}

*I would have used a number datatype in openHAB for the DDWRT Device Count, but curl wants to pass the MIME type “text/plain”, so using a string was my shortcut to get it working.

…Example below, working on my Netgear R6300v2 (same chipset as R7000) with DD-WRT 3.0 “snapshot” build 29300…

DD-WRT Startup Script:
To install… just navigate to “Administration > Commands” cut/paste your modified version of the script into the text box, then click the “save startup” button. Then reboot the router.

#!/bin/sh
###############################################################
# Proximity detection
#
# A script designed to run on a router running DD-WRT to detect certain devices connected to the router.
# It runs at startup and runs continually, checking for a specific list of devices (phones/laptop, etc)
# that connect wirelessly to the router.  Once a device is connected, the OpenHAB status will
# be updated with either an ON or OFF.  Make sure you set up a switch item in OpenHAB for each device
# you want to track.
#
# The searching frequency can be adjusted to be slower/faster depending on your requirements. Searching too fast
# could burden your router.  Too slow might not update the status as necessary for the application.
#


# Make changes below
# MAC address of each device to watch. Don't leave blank.
# For security purposes, if your router requires a password, even if someone could clone the MAC of your
# phone, they would still require the password of your network to link to your router.
# arp command is case sensitive! letters need to be lower case 
# run "arp -i br0" from CLI to check MAC Addresses
macdevice1="ec:00:00:00:f0:09"    #Joe Nexus 6
macdevice2="00:00:00:47:f8:0a"    #Wife Nexus 5
macdevice3="00:00:00:00:c0:0b"    #Samsung TV Hard Wired
macdevice4="f0:00:47:00:00:0c"    #Kid iPad Wi-Fi Radio

#OpenHAB username, password, and IP Address
#username="OPENHAB_USERNAME"
#password="OPENHAB_PASSWORD"
#IPAddr="OPENHAB_IP_ADDRESS"
#port="OPENHAB_PORT"

# OpenHAB switch items to be updated for each tracked MAC
item1="JoePhone"
item2="WifePhone"
item3="SamsungTV"
item4="KidIPAD"

#String Item in openHAB to display total devices connected 
item99="DDWRTDeviceCount" 

# Occupied and unoccupied delay in seconds to check status
# Adjust for shorter/longer wait times.  For instance, when one device is already
# connected, you might want to check less frequently.  This could also delay the
# notification of a disconnect.
delay_occupied=4
delay_unoccupied=2

# initial testing loop count - uncomment the counter near the bottom of the script for testing only. 
limit=120

###############################################
# do not change below here
###############################################

sleep
#initialize internal variables

# status of each MAC. 0=disconnected. 1=connected.  -1 initially forces openHAB update first loop
macconnected1=-1
macconnected2=-1
macconnected3=-1
macconnected4=-1
connected=-1
# total number of currently conencted devices.
currentconnected=0
counter=1

# Initial testing loop.  Will run continually after testing is complete
while [ $counter -lt $limit ]; do

#reset current status. Two variables are used for each device.  The past known status and the current
# status.  Only a change is reported to openHAB.  Otherwise, it would constantly be updating openHAB with
# the current status creating unnecessary traffic for both the router and openHAB
maccurrent1=0;
maccurrent2=0;
maccurrent3=0;
maccurrent4=0;


## Old Case Section Replaced
# compare each device that is currently connected to the MAC devices we want to watch.
# changed the following to chek for each MAC
arpout=$(arp -i br0)

maccurrent1=$(echo $arpout | grep -c $macdevice1)
if [ $maccurrent1 -gt 0 ]; then
   maccurrent1=1
fi

maccurrent2=$(echo $arpout | grep -c $macdevice2)
if [ $maccurrent2 -gt 0 ]; then
   maccurrent2=1
fi

maccurrent3=$(echo $arpout | grep -c $macdevice3)
if [ $maccurrent3 -gt 0 ]; then
   maccurrent3=1
fi

maccurrent4=$(echo $arpout | grep -c $macdevice4)
if [ $maccurrent4 -gt 0 ]; then
   maccurrent4=1
fi


# Look for a change in status from the old known to the current status.
# If it changed, update openHAB. Otherwise it leaves it as is. 
if [ $macconnected1 -ne $maccurrent1 ]; then
     if [ $maccurrent1 -eq 1 ]; then
         macstatus1="ON";
     else
         macstatus1="OFF";
     fi
     curl -X POST -d $macstatus1 -H "Content-Type: text/plain" -i http://$username:$password@$IPAddr:$port/rest/items/$item1
fi

if [ $macconnected2 -ne $maccurrent2 ]; then
     if [ $maccurrent2 -eq 1 ]; then
         macstatus2="ON";
     else
         macstatus2="OFF";
     fi
     curl -X POST -d $macstatus2 -H "Content-Type: text/plain" -i http://$username:$password@$IPAddr:$port/rest/items/$item2
fi

if [ $macconnected3 -ne $maccurrent3 ]; then
     if [ $maccurrent3 -eq 1 ]; then
         macstatus3="ON";
     else
         macstatus3="OFF";
     fi
     curl -X POST -d $macstatus3 -H "Content-Type: text/plain" -i http://$username:$password@$IPAddr:$port/rest/items/$item3
fi

if [ $macconnected4 -ne $maccurrent4 ]; then
     if [ $maccurrent4 -eq 1 ]; then
         macstatus4="ON";
     else
         macstatus4="OFF";
     fi
     curl -X POST -d $macstatus4 -H "Content-Type: text/plain" -i http://$username:$password@$IPAddr:$port/rest/items/$item4
fi

# Update the known status from the current.  Ready for the next loop. 
macconnected1=$maccurrent1;
macconnected2=$maccurrent2;
macconnected3=$maccurrent3;
macconnected4=$maccurrent4;

# Total up the number of devices connected.
let currentconnected=$macconnected1+$macconnected2+$macconnected3+$macconnected4

# Look for a change, and update openHAB.
if [ $connected -ne $currentconnected ]; then
   curl -X POST -d $currentconnected -H "Content-Type: text/plain" -i http://$username:$password@$IPAddr:$port/rest/items/$item99
fi
connected=$currentconnected

# Delay (sleep) depending on the connection status.
# No devices connected could delay less.  Once a device is connected, it could delay longer.
if [ $connected -gt 0 ]; then
    sleep $delay_occupied
    else
    sleep $delay_occupied
fi

#for testing only - uncomment to have the looping stop at X loops defined in variable:  limit.
#let counter=$counter+1
done

Note: I’ve also been playing around with the concept of running the mac addresses and openHAB items into arrays. then looping through the tests/commands through the array, but not everyone has Bash/BusyBox on their DD-WRT instance. So this example is not coded as “concisely” as possible. That said, it should be “#!/bin/sh” on DD-WRT friendly (assuming your device can run arp & curl). As such, it will likely work with older router hardware/firmware builds.

Note2: assuming you have persistence sorted out… Leveraging the example above you could easily modifying the periodic presence detection rules based on a ‘gMobiles’ group as illustrated on the openHAB wiki “Samples-Tricks” page. Link here:

Thanks for putting this together. I was just reading the post you linked to, and got excited with the prospect but was wondering how it would work with my multi radio setup.

I’m still setting everything back up again after moving house, but this is now on the list of things to get working.

1 Like

I have read a lot about this being used to detect smartphones, but I never have my wifi enabled on my phone.

So I was trying to find a keyfob sized battery powered device that could connect via wifi and be used to detect presence. Similar to a Bluetooth beacon. Does such a thing exist or would the size mean that the signal would be so weak that just going with the Bluetooth keyfob be better?

I am not sure about BT, I have never done that. If you went that route it wouldn’t use this script or a dd-wrt router.

I’m an AOSP flavored Android kind of guy, so I let Tasker turn on my WiFi on and off automatically based on geo-fencing rules (when connected to the appropriate cell towers and close to one of my 3 main WiFi use case locations).

It is also nice that this script will allow the connectivity of SmartTV’s and other devices on the network play into the presence detection calculation.

I’m in the USA and have grandfathered unlimited data with Verizon and not super reliant on WiFI, but I use it at home and work.

My wife is on Ting (a Sprint/T-Mobile MVNO) and has a much higher motivation to use WiFi. She leaves her mobile data turned off via the ting dashboard/app (so she only has data connectivity over WiFi). With the Ting “no contracts & only pay for what you use billing” her bill is only $23 a month. She uses around 450 minutes and 900 SMS texts monthly. So needless to say she is constantly on WiFi for data.

Check out their billing structure http://ting.com/rates. If you sign up for service use THIS referral link to save $25 on service or device purchase.

You could try something like a Wireless Sensor Tag Pro which has a memory and will sync its data when reconnected to the wifi network. You might be able to use the http binding to detect it’s data string and determine presence, but you’d have to have the tag poll data every 30seconds or you might be home for however long your polling period is before it recognises your presence.

1 Like

Thank you @jheide44 for your post. :slight_smile:

I use the script now to detect devices on two DD-WRT devices. But I had to modify the script as it was not working for me right away. The main difference here is that I kicked out user and password (as this cannot be set in openHAB 2.0/2.1 at the moment) and also I changed the command which gets the MAC addresses for all online devices arp -i br0 did not work for my Atheros based DD-WRT devices and also original Atheros command from Aaron did only show some devices as already mentioned. But wl_atheros assoclist gets all online devices from all ath wireless interfaces, which is exactly what I wanted here.

You can find the modified script here: https://git.hot-chilli.net/msebald/openhab/src/master/presence/ddwrt.sh

1 Like

You are amazing. Thanks Roi! You fixed it for me as well.

@Roi & @Yellow-Snow

The main difference in commands is based in the Broadcom vs Atheros chip-sets. My original script was on a device with Broadcom chip-set and leveraged the Broadcom commands.

I can’t speak to the user/password or Openhab 2.x… I’m still sporting OpenHAB 1.8.2, I need to upgrade. That said, my “if it isn’t broke, don’t fix it” logic has brought me this far.

Just out of curiosity what devices do you have running DD-WRT? My original script (talking to OpenHAB 1.8.2) is now running on a Netgear R6400.

Glad it is working for you both!

I’m running DD-WRT v3.0-r34411 on TP-Link Archer AC1750 which says "Qualcomm Atheros QCA9558 ver 1 rev 1.0 (0x1130) "

Now i’m digging into the damn iPhone deep sleep mode :frowning: BLAHHHH


Found that tho. hmmm

@jheide44 I am using two TP-Link Archer C7 v2.0 devices (running DD-WRT v3.0-r33525 at the moment, CPU also reports Qualcomm Atheros QCA9558 ver 1 rev 1.0 (0x1130)). What you said is true, the main difference is the chipset thing. It was not hard to find out, but it needed some time and some Google searches to get the right command. Stripping out the user/password thing was easy then. Not nice anyway but everything is done in a local network.

@Yellow-Snow Is your newer DD-WRT build running ok for you? We use the same CPU so upgrading could be interesting for me as well. As I had a lot of bad build for my C7 devices I do not upgrade very often.

@Roi haha funny you asked… my router rebooted all night. Not sure what caused it. The script, my settings, or ddwrt build. Stay tuned.

@Yellow-Snow OMG. :wink: Use the version I mentioned above. It runs stable for me. Including the presence script.

Factory defaults seems like it fixed it. Uptime - 2 days, 20:51
My settings were botched bc I was messing around with WDS to increase my 2.4 range for my cheap Chinese ip cams connected to BI.

Also I dont think im going to use this presence detection. iPhones go into a deep sleep but you can wake it up using

hping3 -2 -c 10 -p 5353 -i u1 xxx.xxx.x.x -q >/dev/null 2>&1 

So far it’s working like a charm, thanks @jheide44 and @Roi for you guys! Great work!
I would like to point out this script can also working great by a padavan router, althrough the wl_atheros by Roi’s can’t work, turns out changing to jheide44’s arp -i br0 can work ^^…

Some questions for the script itself (from a noob-of-view’s dump questions)

  1. delay_occupied=4, delay_unoccupied=2 what does the 4 means(a minute unit)?
  2. limit=120 what does this means?
  3. What if I want to add one more device? Do I need to modify to adding macconnected5 like this one?
    let currentconnected=$macconnected1+$macconnected2+$macconnected3+$macconnected4
  4. How long when the script get executed? All the time or by a period of time? What settings to tweak this time?Here is what the terminal shown after execute the script.
BusyBox v1.24.2 (2018-01-14 12:59:04 CST) multi-call binary.

Usage: sleep [N]...

Pause for a time equal to the total of the args given, where each arg can
have an optional suffix of (s)econds, (m)inutes, (h)ours, or (d)ays
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 0
Server: Jetty(9.3.22.v20171030)

HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 0
Server: Jetty(9.3.22.v20171030)

I’m using a few days old build of DD-WRT, and it doesn’t have curl, although arp works fine. Any workaround for me to get the router to update openHAB? My router doesn’t have a USB port, so I don’t think Optware is an option. Right now, I’m just scraping the rotuer’s info page from my server and sending it through MQTT like this (I already have the MQTT broker running for other things):

#!/usr/bin/python
# -*- coding: utf-8 -*-

import paho.mqtt.client as mqtt
import collections
import urllib2
import re
from bs4 import BeautifulSoup
import time

def on_connect(client, userdata, flags, rc):
	# Subscribing in on_connect() means that if we lose the connection and
	# reconnect then subscriptions will be renewed.
	print("Connected with result code "+str(rc))
	client.subscribe("router/unknownusers/status")
	for knownname in knownmacs.values():
		if knownname != 'Ignore' :
			client.subscribe("router/user/" + knownname + "/status")


knownmacs = { 'DE:AD:BE:EF:FA:D1': 'MansPhone', 'DE:AD:BE:EF:FA:D2': 'WomansPhone', 'DE:AD:BE:EF:FA:D3' : 'MansWatch', 'DE:AD:BE:EF:FA:D4' : 'WomansWatch', 'DE:AD:BE:EF:FA:D5': 'Harmony', 'DE:AD:BE:EF:FA:D6': 'PortableSpeaker', 'DE:AD:BE:EF:FA:D7': 'Ignore', 'DE:AD:BE:EF:FA:D8': 'Ignore', 'DE:AD:BE:EF:FA:D9': 'Ignore' }

quote_page = 'http://192.168.1.1/Info.htm'

client = mqtt.Client()
client.on_connect = on_connect
client.username_pw_set(username='somemqttuser', password='somemqttpassword')
mqtthost = "192.168.1.2"
mqttport = 1883
mqttkeepalive = 60

switches = {}
for name in knownmacs.values():
	switches[name] = "NULL"

unknowns = 100
i = 0
while True:
	page = urllib2.urlopen(quote_page)
	soup = BeautifulSoup(page, 'html.parser')
	soupstring = str(soup)

	# Search for MAC addresses
	X = '([a-fA-F0-9]{2}[:|\-]?){6}' # this is the regex
	c = re.compile(X).finditer(soupstring)
	macs = []
	if c:
		for y in c:
			macs.append(soupstring[y.start(): y.end()])

	onlinemacs = [item for item, count in collections.Counter(macs).items() if count > 1]
	unknowncount = 0
	onlinenames = []
	for mac in onlinemacs:
		try:
			if knownmacs[mac] != 'Ignore' :
				onlinenames.append(knownmacs[mac])
		except KeyError :
			unknowncount += 1

	while True:
			try:
				client.connect(mqtthost, mqttport, mqttkeepalive)
				break
			except:
				pass

	for knownname in knownmacs.values():
		if knownname in onlinenames and knownname != 'Ignore' :
			if ( switches[knownname] != 'ON' ) or ( i % 24 == 0 ):
				client.publish(topic="router/user/" + knownname + "/status", payload="ON", qos=0, retain=False)
				print knownname + " ON"
			switches[knownname] = "ON"
		elif  knownname != 'Ignore' :
			if ( switches[knownname] != 'OFF' ) or ( i % 24 == 0 ):
				client.publish(topic="router/user/" + knownname + "/status", payload="OFF", qos=0, retain=False)
				print knownname + " OFF"
			switches[knownname] = "OFF"

	if ( unknowncount != unknowns ) or ( i % 24 == 0 ):
		print str(unknowncount) + " Unknown count"
		client.publish(topic="router/unknownusers/status", payload=unknowncount, qos=0, retain=False)
		unknowns = unknowncount
	time.sleep(5)
	i += 1
end
String Unknownwifi { mqtt="<[broker:router/unknownusers/status:state:default], >[broker:router/unknownusers/status:command:*:default]" }
Switch MansPhone { mqtt="<[broker:router/user/MansPhone/status:state:default], >[broker:router/user/MansPhone/status:command:*:default]" }
Switch WomansPhone { mqtt="<[broker:router/user/WomansPhone/status:state:default], >[broker:router/user/WomansPhone/status:command:*:default]" }
etc...

It works fine, but I was wondering if anyone has a better solution without curl and Optware. I’d rather just have the router send the info directly to openHAB if possible.

Thanks!

Have they removed curl? That’s bad news indeed… I’m using a versions that’s a couple months old and curl woks fine here. Why would they do such a thing?

Using curl or Optwire IS having the router send the message ditectly to OH.

Does wget still exist? You might be able to make something work with that. If not I don’t see how you can do it without installing these tools back on your router.

I’m aware, and that’s what I want, just can’t seem to do it with the things I’m working with. It does have wget, but it’s a really stripped down BusyBox version. The post attributes (–post-data and --post-file), and tons of other things, aren’t there.

It does have things I could send files with, like scp, but I’m not sure if doing anything like that is any better than web scraping.

@DanielMalmgren Maybe it’s just my router? It has pretty small flash by today’s standards (16MB), but I’m sure I’ve had routers with smaller flash in the past that had it. My router is a Dlink DIR859.

There is a not officially supported way to change an items state using GET which might work with the stripped down wget that is available.

You need to install ClassicUI I believe.

That can get you by until another solution presents itself.

1 Like

Awesome! Thanks! Looks like I have something to try, and I’ll report back. I really appreciate all you write on here too. Seems like your posts answer most of my questions outside of the guide.

Edit:
Okay, that works like a charm. Thanks again!