Monthly Archives: July 2014

Checking who is home with PowerShell

The first thing that comes to mind when doing home automation is probably controlling lights and sockets depending on if someone is home or not. Most apps I’ve seen for this can’t handle multiple family members though, which is quite useless. You don’t want to turn on all the lights when you come home if someone else is sleeping or watching a movie with dimmed lights for example.

So how to figure out who is currently at home?

The method I chose was to check if our phones are connected to WiFi. There are a few obstacles to overcome before this works though, for example:

  • Many smartphones turn off WiFi to save battery (for example the iPhone)
  • They use dynamic IP-addresses and name resolution is not always available, so how to find them?
  • The script therefor checks for how long a phone has been offline before it does any changes, and it starts off by doing a pingsweep to populate the arp-table to find the different phones MAC-addresses.

    This scripts updates the status of each phone in a file on disk and also changes a device in Telldus Live! to On/Off. That makes it possible to filter events in Telldus Live! based on who’s home, or when the last person leaves.

    The script-module used here can be found in this post.

    The script looks like this:

    # Import the telldus module
    Import-Module 'C:\TelldusScripts\Telldusv2.psm1'
    
    # Set telldus credentials
    $Username = "[email protected]"
    
    # Get the password from the file (a saved PowerShell credential)
    $Password = Get-Content 'C:\TelldusScripts\TelldusPassword.txt' | ConvertTo-SecureString
     
    # Build the credential
    $TelldusCredential = New-Object System.Management.Automation.PsCredential($Username,$Password)
    
    # Specify path to the "FamilyMember" file
    $FamilyMemberFile = "C:\TelldusScripts\FamilyMembers.csv"
    
    # How many times should we wait for the network scan to finish?
    $MaxWaitRounds = 10
    
    # How long should we sleep between each "network scan check"?
    $WaitBetweenNetworkScanSeconds = 10
    
    # Load that file into an array
    $FamilyMembers = Import-Csv $FamilyMemberFile -Encoding utf8
    
    # Get the subnets
    $DeviceSubnets = $FamilyMembers | select -ExpandProperty WiFiSubnet | Sort-Object -Unique
    
    # Set the time limit for when a device should be considered offline
    $NotHomeTimeLimit = 120
    
    # Do a ping sweep in those subnets to find any device MACs
    foreach ($DeviceSubnet in $DeviceSubnets) {
    
        Start-Job -ArgumentList $DeviceSubnet -ScriptBlock { $Subnet=$args[0] ; 1..25 | % { ping "$Subnet$_" -n 1 -w 1 } }
        Start-Job -ArgumentList $DeviceSubnet -ScriptBlock { $Subnet=$args[0] ; 26..50 | % { ping "$Subnet$_" -n 1 -w 1 } }
        Start-Job -ArgumentList $DeviceSubnet -ScriptBlock { $Subnet=$args[0] ; 51..75 | % { ping "$Subnet$_" -n 1 -w 1 } }
        Start-Job -ArgumentList $DeviceSubnet -ScriptBlock { $Subnet=$args[0] ; 76..100 | % { ping "$Subnet$_" -n 1 -w 1 } }
        Start-Job -ArgumentList $DeviceSubnet -ScriptBlock { $Subnet=$args[0] ; 101..125 | % { ping "$Subnet$_" -n 1 -w 1 } }
        Start-Job -ArgumentList $DeviceSubnet -ScriptBlock { $Subnet=$args[0] ; 126..150 | % { ping "$Subnet$_" -n 1 -w 1 } }
        Start-Job -ArgumentList $DeviceSubnet -ScriptBlock { $Subnet=$args[0] ; 151..175 | % { ping "$Subnet$_" -n 1 -w 1 } }
        Start-Job -ArgumentList $DeviceSubnet -ScriptBlock { $Subnet=$args[0] ; 176..200 | % { ping "$Subnet$_" -n 1 -w 1 } }
        Start-Job -ArgumentList $DeviceSubnet -ScriptBlock { $Subnet=$args[0] ; 201..225 | % { ping "$Subnet$_" -n 1 -w 1 } }
        Start-Job -ArgumentList $DeviceSubnet -ScriptBlock { $Subnet=$args[0] ; 225..255 | % { ping "$Subnet$_" -n 1 -w 1 } }
    }
    
    $NumberOfRounds = 1
    
    while ((Get-Job -State Running).count -gt 0 -AND $NumberOfRounds -le $MaxWaitRounds) {
        Write-Output "Scanning network for devices... (Round: $NumberOfRounds of max $MaxWaitRounds. Number of jobs running: $((Get-Job -State Running).count).)"
        sleep -Seconds $WaitBetweenNetworkScanSeconds
        $NumberOfRounds++
    }
    
    
    # Loop through the family members
    foreach ($FamilyMember in $FamilyMembers) {
    
        # Reset the variables
        $FamilyMemberLastStatus = $null
        $FamilyMemberCurrentStatus = $null
    
        # Get last status
        $FamilyMemberLastStatus = Get-Content $($FamilyMember.StatusFile)
        
        # Get current status by name
        $FamilyMemberCurrentStatus = Test-Connection $($FamilyMember.DeviceName) -Count 1 -Quiet
    
        # Get it by MAC if that failed
        if ($FamilyMemberCurrentStatus -eq $false) {
            $CurrentFamilyMemberIP = $null
            $CurrentFamilyMemberIP = arp -a | select-string $FamilyMember.DeviceMAC | % { $_.ToString().Trim().Split(" ")[0] } | select -First 1
    
            $FamilyMemberCurrentStatus = Test-Connection $CurrentFamilyMemberIP -Count 1 -Quiet
        }
        # Check if the current status is online
        if ($FamilyMemberCurrentStatus -eq $true) {
            
            Write-Output "$($FamilyMember.Name)'s device is online!"
    
            # Update status file
            Write-Output $true | Out-File $FamilyMember.StatusFile
    
            # If it was offline last time, set the telldus device to on
            if ($FamilyMemberLastStatus -ne $true) {
                Write-Host "This is a change since last run, setting device to on."
                Connect-TelldusLive -Credential $TelldusCredential
                Set-TDDevice -DeviceID $FamilyMember.DeviceID -Action turnOn
            }
        }
        # So the device was offline...
        else {
            Write-Output "$($FamilyMember.Name)'s device is offline!"
    
            # Verify if this is temporary (iPhone sleep mode) or if it has been offline for a while
            $LastChangeTime=(gci $($FamilyMember.StatusFile)).LastWriteTime
            $Now=Get-Date
            $LastChangeTimeSpan = (New-TimeSpan -Start $LastChangeTime -End $Now).TotalMinutes
    
            # Only change status if the device has been offline for $NotHomeTimeLimit minutes.
            if ($LastChangeTimeSpan -gt $NotHomeTimeLimit -AND $FamilyMemberLastStatus -eq $true) {
                # It was, update statusfile to offline
                Write-Output $false | Out-File $($FamilyMember.StatusFile)
    
                Write-Output "This device has been offline for more than $NotHomeTimeLimit minutes. Turning Telldus device off..."
                Connect-TelldusLive -Credential $TelldusCredential
                Set-TDDevice -DeviceID $FamilyMember.DeviceID -Action turnOff
            }
        }
    }
    

    I’ve scheduled it to run quite often to always check if someone has arrived. Hopefully the comments in code will give you the help you need, if not, post a comment below and I’ll be glad to help you!

    The csv file ($FamilyMemberFile, C:\TelldusScripts\FamilyMembers.csv) used in the script above should have the columns Name, DeviceName, DeviceMAC, WiFiSubnet, StatusFile, DeviceID.

    Name = The name of the person who owns the device
    DeviceName = The DNS name of the device. iPhones use “DeviceName.local”
    DeviceMAC = MAC-address of the devices, use “-” as a separator.
    WiFiSubnet = The subnet where the phone gets its IP-address.
    StatusFile = The path to a file unique for this phone where the status can be written (if it’s home or not)
    DeviceID = The device id in Telldus Live! that is used to filter if this person is home.

    Example:
    Name,DeviceName,DeviceMAC,WiFiSubnet,StatusFile,DeviceID
    John,”JohnsiPhone.local”,”00-01-03-04-05-06″,”192.168.1.”,”C:\TelldusScripts\IsJohnHome.txt”,”123456″

    The script will loop through all devices added and set the status in the status file (True/False) aswell as the device in Telldus Live! (On/Off) depending on if the phone was online or not.

    Hope that made sense for you!