Of course, my wife burst my bubble when she very astutely pointed out: "What happens when we both go someplace in one car and leave the other one home?" I then tried to make the RFID idea work by suggesting she carry the transponder in her purse, and I'd carry the other in my pocket, but I really didn't want one more thing to carry around with me.
Which is when I realized: I already carry a computer with me all the time, my smartphone.
So I made a few quick Google searches to see if I was trying to re-invent the wheel or not, and came up with this: DHCP Detection. Basically, the OpenWRT router will power up a server when the specified clients are assigned DHCP leases, and power it down when they leave the network.
I set up a quick cron job on the Raspberry Pi to automatically run a simple script on the OpenWRT router every 5 minutes:
--presenceDetect.sh-- #!/bin/sh present=`ssh root@192.168.1.1 'cat /tmp/dhcp.leases | grep -c hostName'` echo $presentThe -c parameter to grep just counts the lines that contain the word hostName, so you would replace "hostName" with the name or MAC of your choice.
Unfortunately, my plans were dashed to the ground once again when I disabled wi-fi on my phone to test it, and realized that my DHCP lease was still present, and would not expire for several more hours.
A bit more google searching and I came across this page in the OpenWRT wiki: OpenWRT Wireless FAQ. With my hardware, the "iw" command was the one to use. So I updated my test script, replacing 'cat /tmp/dhcp.leases' with 'iw dev wlan0 station dump' and I was off and running. I could disable wi-fi on my phone, issue the command, and see 0. I could re-enable wi-fi, rerun the command, and see '1'. Wonderful. So I wrote a little Perl script to get the router's station list, poll it for my phone and my wife's phone, keep a little bit of state, add a "Transition" timer, so it would take more time to flag an "away" event than it would a "home" event, and set that to run every 5 minutes.
I've attached the content of that script, if you're curious, but full disclosure, I *HATE* Perl, so it may make some of you monks out there cry.
#!/usr/bin/perl
# Presence Detect -- A simple script to query the router's Active
# Devices table for a few MAC addresses, and store the results of
# the presence to a file. In the event that the presence has
# updated, it will trigger some events accordingly
#
# STATE MACHINE DESCRIPTION
#
# +----------+ +----------+ +----------+
# | | p(0) | | | |
# | |-------->| HOME | p(0) | |
# | HOME | | \/ |-------->| AWAY |
# | | p(1) | AWAY | EVENT | |
# | |<--------| | | |
# +----------+ +----------+ +----------+
# ^ :
# | p(1) |
# +-----------------------------------------+
# EVENT
#
# The file name to store persistent presence data in
$PRESENCE_FILE = ".presence.state";
$EVENT_FILE = "events.log";
$GREP_STR = "ssh root\@192.168.1.1 'iw dev wlan0 station dump | grep Station'";
# The mac address to check the presence of, and friendly names
# associated with them
@MAC_ADDRS = ( "00:00:00:00:00:00", "00:00:00:00:00:01" );
@MAC_NAMES = ( "FriendlyName1", "FriendlyName2" );
# Define constants for the state - Home, Transitioning, and Away
use constant HOME => 'Home';
use constant TRANS => 'Trans';
use constant AWAY => 'Away';
# Read the current presence information from the server
@stationList = qx( $GREP_STR );
# Grep the station list for the desired MAC addresses
for my $i (0 .. $#MAC_ADDRS) {
$presence[$i] = grep( /$MAC_ADDRS[$i]/, @stationList );
}
# Attempt to open the file for reading, creating it if it doesn't exist
open ( FILE, "<", $PRESENCE_FILE ) or die $!;
# Read the current state from the file, and close it
chomp(@curState = );
close FILE;
# If the file is empty, update the current states with unknowns, which
# will cause the file to get re-written later
if( scalar(@curState) == 0 ){
print "Existing State File Reference was Empty, generating new!\n";
foreach (@MAC_ADDRS){
push( @curState, 'Unk' );
}
}
# Iterate over the states dumped to the file
for my $i (0 .. $#curState){
#print "Current State for $MAC_NAMES[$i] is \'$curState[$i]\'\n";
if( $curState[$i] eq HOME ){
if( $presence[$i] ){
# Do nothing
}
else{
# If we're HOME and presence is false, then go to a transition state
$curState[$i] = TRANS;
}
}
elsif ( $curState[$i] eq TRANS ){
if( $presence[$i] ){
# If we're in a transition and presence is true, then fall back to home
$curState[$i] = HOME;
}
else{
# If we're in a transition and presence is false, then we're really away
$curState[$i] = AWAY;
ThrowEvent( AWAY, $MAC_NAMES[$i] );
}
}
elsif( $curState[$i] eq AWAY ){
if( $presence[$i] ){
# If we're away, and presence is true, go directly to home, no transition
$curState[$i] = HOME;
ThrowEvent( HOME, $MAC_NAMES[$i] );
}
}
else{
if( $presence[$i] ){
$curState[$i] = HOME;
}
else{
$curState[$i] = AWAY;
}
}
#print "New State is $curState[$i]\n";
}
# Now Re-Open the file for writing
open ( FILE, ">", $PRESENCE_FILE ) or die $!;
# Write the new states back out to the file
foreach( @curState ){
print FILE "$_\n";
};
# Close the file again
close FILE;
# A simple subroutine to thrown an event when a state transition is
# detected
sub ThrowEvent {
($type, $user) = @_;
use POSIX qw(strftime);
$unixTime = time();
$localTimeStr = strftime "%D %l:%M %p", localtime($unixTime);
$color = "#00FF00";
open ( EVENTS, ">>", $EVENT_FILE ) or die $!;
print EVENTS "$unixTime$color:\"$localTimeStr: $user is $type\"\n";
close ( EVENTS );
}
So there you have it. It's not perfect, and it's not optimal, but optimizing it is a task for another day. I have a few ideas in mind, including using SNMP rather than ssh polling, as well as making it more asynchronous using TCP sockets from one machine to another.
Feel free to weigh in with any comments.
No comments:
Post a Comment