MiHome and Nest Home Automation

5 Apr

Following on from my previous post, I did a bit of work to simplify the code and make it easier to add new tasks. There is now one master function that you can call to create all the task events and pass in the parameters:

#run this in the morning at 2 am to set everything for the rest of the day


$global:taskid = 0
$rootdir = "c:\home_automation\"
$scriptdir = $rootdir + "scripts\"
$logdir = $rootdir + "logs\"
$logfile = $logdir + $(Get-Date).ToString("yyyy_MM_dd") + "_events.log"
$staskprefix = "home_automation_"
$nestaccesstoken = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$weatherapikey = "xxxxxxxxxxxxxxxxxxxxxxxxxx"
$thermostatid = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$structureid = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$mihomeuser = "xxxxxxxxxxxxxxxxxxx"
$mihomepass = "xxxxxxxxxxxxxxxxxx"
$weatherlocation = "Home,uk"
$logagelimit = (Get-Date).AddDays(-15)

#get day of week as an int, sun=0, mon=1 etc...
$dayofweek = [int] (get-date).DayOfWeek



function writelog($logdata){
    Add-content $logfile -value ("$(Get-Date): $logdata")
    #Write-Host $logdata

function IsTermDay {
    #assume is not a term day to start
    $istermdate = $false

    $todaysdate = get-date
    #$todaysdate = [datetime]::ParseExact('28/05/2018','dd/MM/yyyy',$null)
    #write-host $todaysdate

    $url = "https://www.birmingham.gov.uk/info/20014/schools_and_learning/685/school_term_dates/1"
    $result = Invoke-WebRequest $url 
    $mydata = $result.AllElements 
    $separator = " "

    #so now we need to check todays date against all the term dates
    #if today is between outerstart and outerend, but not in the range inner start and inner end

    foreach ($element in $mydata)

    if($element.tagName -eq 'P'){
            $mydata = $element.innerHTML.ToString()

            $mydata = $mydata.Replace("Term Starts: ","")
            $mydata = $mydata.Replace("Half Term: ","")
            $mydata = $mydata.Replace("Term Ends: ","")
            $mydata = $mydata.Replace("
"," ")
            $mydata = $mydata.Replace("to ","")
            #write-host $mydata

            $splitevent = $mydata -split{$separator -contains $_}
            $outerstartdate = [datetime]::ParseExact($splitevent[1] + "-" + $splitevent[2] + "-" + $splitevent[3], "d-MMMM-yyyy", $null)
            $outerenddate = [datetime]::ParseExact($splitevent[13] + "-" + $splitevent[14] + "-" + $splitevent[15], "d-MMMM-yyyy", $null)

            $innerstartdate = [datetime]::ParseExact($splitevent[5] + "-" + $splitevent[6] + "-" + $splitevent[7], "d-MMMM-yyyy", $null)
            $innerenddate = [datetime]::ParseExact($splitevent[9] + "-" + $splitevent[10] + "-" + $splitevent[11], "d-MMMM-yyyy", $null)

            if($todaysdate -ge $outerstartdate -and $todaysdate -le $outerenddate){
                $istermdate = $true
                if($todaysdate -ge $innerstartdate -and $todaysdate -le $innerenddate){
                    $istermdate = $false



    return $istermdate

$todayistermday = IsTermDay


#find out if today is a bank holiday
$result = Invoke-WebRequest "https://www.gov.uk/bank-holidays.json" -Method Get | convertfrom-json
$today = Get-Date -format "yyyy-MM-dd"
$bankholidaytoday = $false
foreach($event in $result.'england-and-wales'.events)
    if($event.date -eq $today){
        $bankholidaytoday = $true


#Get Weather information and sun rise/sunset
$result = Invoke-WebRequest "http://api.openweathermap.org/data/2.5/weather?q=$weatherlocation&APPID=$weatherapikey&units=metric" -method Get -UseBasicParsing | convertfrom-json
$outsidetemp = $result.main.temp
$origin = New-Object -Type DateTime -ArgumentList 1970, 1, 1, 0, 0, 0, 0
$sunrise = $origin.AddSeconds($result.sys.sunrise)
$sunset = $origin.AddSeconds($result.sys.sunset)


#set mihome device ID variables
$user= $mihomeuser
$pass = $mihomepass
$pair = "${user}:${pass}"
$bytes = [System.Text.Encoding]::ASCII.GetBytes($pair)
$base64 = [System.Convert]::ToBase64String($bytes)
$basicAuthValue = "Basic $base64"
$headers = @{ Authorization = $basicAuthValue }
$emptyJSON = @{}  | ConvertTo-Json
$subdevicesuri = "https://mihome4u.co.uk/api/v1/subdevices/list"
$result = Invoke-WebRequest -Headers $headers -uri $subdevicesuri -Method put -Body $emptyJSON -ContentType "application/json" -UseBasicParsing | ConvertFrom-Json

$result.data | select-object id, label | foreach-object {
    $varname = "var_" + $_.label -replace " ", "_"
    if(!(Get-Variable $varname)){
        New-Variable -Name $varname.ToLower() -value $_.id

#get-variable | where {$_.Name -like 'var_*'}


#get day of week as an int, mon=1, sun=0 etc...
$dayofweek = [int] (get-date).DayOfWeek


#check files and folder we need exist, and if not create them
if(!(Test-Path $rootdir)){
    new-item -itemtype directory -path $rootdir

if(!(Test-Path $scriptdir)){
    new-item -itemtype directory -path $scriptdir

if(!(Test-Path $logdir)){
    new-item -itemtype directory -path $logdir

if(!(Test-Path $logfile)){
    new-item -itemtype file -path $logfile

#clear existing powershell files
Get-ChildItem -Path $scriptdir -Include *.* -File -Recurse | foreach { $_.Delete()}

#clear existing scheduled tasks
Unregister-ScheduledTask -TaskName "$staskprefix*" -Confirm:$false

writelog "Sunrise at $sunrise"
writelog "Sunset at $sunset"

writelog "Creating Scheduled Tasks for Today"


# Delete log files older than the $limit.
Get-ChildItem -Path $logdir -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit } | Remove-Item -Force



function Set-Automation {
  <# .SYNOPSIS This is the master function to create all the scheduled tasks .DESCRIPTION Pass in all the conditions and objects and off it goes, or on of course .EXAMPLE Set-Automation -starttime $sunset -homeaway "away" -onoff "on" -devicetype "power" -devices $var_lounge_light .EXAMPLE Set-Automation -starttime "23:00" -dayrange 0,1,2,3,4 -onoff "off" -devicetype "power" -devices $var_lounge_light, $var_lounge_socket, $var_dining_room_light, $var_lounge_tv, $var_lounge_socket .PARAMETER recurringinterval Optional, frequency in minutes to reoccur .PARAMETER dayrange Optional. Comma seperated days at ints, 1,2,3,4,5 .PARAMETER devices MiHome device IDs, comma seperated .PARAMETER onoff Choose to turn things on or off .PARAMETER heatingstatus Set the required heating status, "heat" or "off" .PARAMETER homeaway Set the required homeaway status, "home" or "away" .PARAMETER temp Set the temperature for the vent to occur at .PARAMETER tempminmax Is this the minimum or maximum temperature, "Min" or "Max" .PARAMETER donotrunonbankholiday Optional. Set to $true if you dont want it on a bank holiday .PARAMETER dontrunoutoftermtime Optional. Set to $true if you dont want it outside of term dates .PARAMETER devicetype Set to either "power" or "heating" depending on what you want to control #>


    begin {

        writelog "==================================="

        #increment the task id
        $global:taskid += 1

        writelog "Creating new Task ID: $global:taskid"
        writelog "Parameters:"
        writelog "-----------------------------"

        #list all the supplied parameters
        $ParameterList = (Get-Command -Name $MyInvocation.InvocationName).Parameters;
            foreach ($key in $ParameterList.keys)
                $var = Get-Variable -Name $key -ErrorAction SilentlyContinue;
                    writelog "$($var.name) > $($var.value)"
        writelog "-----------------------------"


    process {

        writelog "Today is day number $dayofweek"
        #lets check the day range if set
        if($dayofweek -in $dayrange -xor $dayrange -eq $null){

            writelog "In day range, continuing."

            #always run unless today is a bank holiday and dontrunonbankholiday set as true
            if(!($bankholidaytoday -and $dontrunonbankholiday -eq $true)){
                writelog "Passed bank holiday check, continuing."
                #always run unless $todayistermday is false and dontrunoutofterm is true
                if(!($todayistermday -eq $false -and $dontrunoutofterm -eq $true)){
                    writelog "Passed term dates check, continuing."

                    #thats all the initial conditions met, lets build the task
                    $scriptpath = $scriptdir + $staskprefix + $global:taskid + ".ps1"
                    writelog "Script Path: $scriptpath"

                    #lets create the script file basics
                    $scriptstart = '
                    Add-content '+$logfile+' -value "$(Get-Date): Running Job ID '+$global:taskid+'"
                    $result = Invoke-WebRequest "http://api.openweathermap.org/data/2.5/weather?q='+$weatherlocation+'&APPID='+$weatherapikey+'&units=metric" -method Get -UseBasicParsing | convertfrom-json
                    $outsidetemp = $result.main.temp

                    $homeawayuri = "https://developer-api.nest.com/structures/'+$structureid+'/away?auth='+$nestaccesstoken+'"
                    $homeaway = Invoke-WebRequest $homeawayuri -Method Get -UseBasicParsing
                    $homeaway = ($homeaway -replace """","")

                    $heatoffuri = "https://developer-api.nest.com/devices/thermostats/'+$thermostatid+'?auth='+$nestaccesstoken+'"
                    $heatofftemp = Invoke-WebRequest $heatoffuri -Method Get -UseBasicParsing | convertfrom-json
                    $heatoff = ($heatofftemp.hvac_mode -replace """","")

                    $user= "'+$mihomeuser+'"
                    $pass = "'+$mihomepass+'"
                    $pair = "${user}:${pass}"
                    $bytes = [System.Text.Encoding]::ASCII.GetBytes($pair)
                    $base64 = [System.Convert]::ToBase64String($bytes)
                    $basicAuthValue = "Basic $base64"
                    $headers = @{ Authorization = $basicAuthValue }

                    $scriptopening = ''
                    $scriptclosing = ''

                    #now lets build the conditions within the script that will run
                    #check home away status
                    if($homeaway -ne ""){
                        writelog "HomeAway requred to be: $homeaway"  
                        $scriptopening += 'if($homeaway -eq "'+$homeaway+'"){'
                        $scriptopening += 'Add-content '+$logfile+' -value "$(Get-Date): Passed. Homeaway set to $homeaway"'
                        $scriptclosing += '}else{' 
                        $scriptclosing += 'Add-content '+$logfile+' -value "$(Get-Date): Failed. Homeaway set to $homeaway"'
                        $scriptclosing += '}' 

                    #check heating status
                    if($heatingstatus -ne ""){
                        writelog "Heating Status requred to be: $heatingstatus"  
                        $scriptopening += 'if($heatoff -eq "'+$heatingstatus+'"){'
                        $scriptopening += 'Add-content '+$logfile+' -value "$(Get-Date): Passed. Heating set to $heatoff"'
                        $scriptclosing += '}else{'  
                        $scriptclosing += 'Add-content '+$logfile+' -value "$(Get-Date): Failed. Homeaway set to $heatoff"'
                        $scriptclosing += '}' 

                    #check outside temperature
                    if($temp -ne 0 -and $tempminmax -ne $null){
                        writelog "Outside temperature required $tempminmax of $temp" 
                        if($tempminmax -eq "Min"){
                            $scriptopening += 'if($outsidetemp -gt '+$temp+'){'
                            $scriptopening += 'Add-content '+$logfile+' -value "$(Get-Date): Passed. Outsiode temp is $outsidetemp"'
                            $scriptopening += 'if($outsidetemp -le '+$temp+'){'
                            $scriptopening += 'Add-content '+$logfile+' -value "$(Get-Date): Passed. Outside temp is $outsidetemp"'
                        $scriptclosing += '}else{'  
                        $scriptclosing += 'Add-content '+$logfile+' -value "$(Get-Date): Failed. Outside temp is $outsidetemp"'
                        $scriptclosing += '}'

                    $scriptmain = ''

                    #now lets see what type of task we are building
                        "power" {
                         writelog "Task Type: Power"

                             foreach($device in $devices){
                                writelog "Adding: $device"
                                $scriptmain += '
                                Add-content '+$logfile+' -value "$(Get-Date): Setting '+$device+' to '+$onoff+' "
                                $JSON = ''{"id":'+$device+'}''
                                $uri = "https://mihome4u.co.uk/api/v1/subdevices/power_'+$onoff+'"
                                $result = Invoke-WebRequest -Headers $headers -uri $uri -Method put -body $JSON -ContentType "application/json" -UseBasicParsing | ConvertFrom-Json

                        "heating" {
                        writelog "Task Type: Heating"

                        if($onoff -eq "on"){
                            $hvacmode = "heat"
                            $hvacmode = "off"

                        $script += '
                        Add-content '+$logfile+' -value "$(Get-Date): Setting heating to '+$heatoff+' "
                        $JSON = ''{"hvac_mode":"'+$hvacmode+'"}''
                        invoke-webrequest "https://developer-api.nest.com/devices/thermostats/'+$thermostatid+'?auth='+$nestaccesstoken+'" -Method Put -Body $JSON -ContentType "application/json -UseBasicParsing"


                    $script = $scriptstart + $scriptopening + $scriptmain + $scriptclosing

                    #lets get to work setting the scheduled task now
                    $script | Out-File $scriptpath
                    $action = New-ScheduledTaskAction -Execute 'Powershell.exe' -Argument "-ExecutionPolicy Bypass  $scriptpath -RunType $true -path $scriptdir"
                    #check now for single run or recurring
                    if($recurringinterval -eq 0){
                        $trigger =  New-ScheduledTaskTrigger -Once -At $starttime
                        $trigger =  New-ScheduledTaskTrigger -Once -At $starttime -RepetitionInterval (New-TimeSpan -Minutes $recurringinterval)

                    Register-ScheduledTask -Action $action -Trigger $trigger -TaskName ($staskprefix+$global:taskid) -Description "Automated Task" -User "NT Authority\SYSTEM" -RunLevel Highest
                    writelog "Creating Scheduled Task ID $global:taskid"

                    writelog "Failed term dates check, exiting."

                writelog "Failed bank holiday check, exiting."
            writelog "Outside of day range, exiting."

        writelog "==================================="


So I can now call the following to set up all the events with:

#1 if temp outside >14 and heating set to heat then turn heating off, <=14 and heating set to odd then turn it on
Set-Automation -starttime "02:00" -recurringinterval 60 -temp 14 -tempminmax "min" -heatingstatus "heat" -devicetype "heating" -onoff "off"
Set-Automation -starttime "02:00" -recurringinterval 60 -temp 14 -tempminmax "max" -heatingstatus "off" -devicetype "heating" -onoff "on"
#2 at 10pm if outside temp <=12 then turn electric blanket on
Set-Automation -starttime "22:00" -homeaway "home" -temp 12 -tempminmax "max" -onoff "on" -devicetype "power" -devices $var_electric_blanket
#3 at 7am turn electric blanket off
Set-Automation -starttime "07:00" -onoff "off" -devices $var_electric_blanket -devicetype "power"
#4 at 7am, turn mater bedroom light on, 7:20 if its not in term time, and not at all if its  bank holiday
Set-Automation -starttime "07:00" -dayrange 1,2,3,4,5 -homeaway "home" -onoff "on" -devicetype "power" -devices $var_master_bedroom_light -dontrunonbankholiday $true -dontrunoutofterm $true
Set-Automation -starttime "07:20" -dayrange 1,2,3,4,5 -homeaway "home" -onoff "on" -devicetype "power" -devices $var_master_bedroom_light -dontrunonbankholiday $true 
#5 sat and sun, turn master bedroom light on at 8am
Set-Automation -starttime "08:00" -dayrange 0,6 -homeaway "home" -onoff "on" -devicetype "power" -devices $var_master_bedroom_light
#6 at sunset if away, turn lounge light on
Set-Automation -starttime $sunset -homeaway "away" -onoff "on" -devicetype "power" -devices $var_lounge_light
#7 turn off all downstairs kit at 23:00 sun-thu, and 23:59 fri-sat
Set-Automation -starttime "23:59" -dayrange 5,6 -onoff "off" -devicetype "power" -devices $var_lounge_light, $var_lounge_socket, $var_dining_room_light, $var_lounge_tv, $var_lounge_socket
Set-Automation -starttime "23:00" -dayrange 0,1,2,3,4 -onoff "off" -devicetype "power" -devices $var_lounge_light, $var_lounge_socket, $var_dining_room_light, $var_lounge_tv, $var_lounge_socket
#8 every 5 mins check if away, and turn of kit and lights, except lounge
Set-Automation -starttime "02:00" -recurringinterval 5 -onoff "off" -devicetype "power" -homeaway "away" -devices $var_lounge_socket, $var_dining_room_light, $var_lounge_tv, $var_master_bedroom_light, $var_kid1_bedroom_light, $var_kid2_bedroom_light 
#9 at 10am, mon-fri, turn off bedroom lights unless its outside of term time or a bank holiday
Set-Automation -starttime "10:00" -dayrange 1,2,3,4,5 -onoff "off" -devicetype "power" -devices $var_master_bedroom_light, $var_kid1_bedroom_light, $var_kid2_bedroom_light -dontrunoutofterm $true -dontrunonbankholiday $true