The Ultimate Smart Home Integration Cookbook

Integration: It enables the smart home hub to communicate and work with various smart devices or services.

Life360 is a family locator app for keeping your loved ones safe. The Life360 integration connects your smart home hub to your Life360 account. This integration can detect your family’s presence, and your smart home can react accordingly. In addition, the Life360 integration is a quick and easy way to manage your hub’s modes and presence-based automations.

Automation Details

Requirements

  • Life360 Service

Actions:

  • Use Life360 to Detect Presence

Learn more about Life360

Novice
Novice

Hubitat

Life360 Connector

The Official Documentation for Life360 Connector

Setting Up the Automation

  1. First, go to Apps and press Add Built-in App button.

    Hubitat App Screen
  2. Please scroll down to you see Life360 Connector and select it.

    Hubitat Install App Screen
  3. Select Enter Life360 credentials

    Hubitat Life360 Settings
  4. Enter your Life360 credentials and click the Done button.

    Hubitat Life360 Credentials
  5. On the Life360 Connector page,

    1. Select your Circle
    2. Select your Place
    3. Select Family Members
    4. Click the Done button

    Success: That’s it, Life360 Connector installed.

    Hubitat Life360 Settings
  6. While Life360 does a pretty good job alone.  There have been times when I was home alone, and the home went into away mode.

    When you install Life360, it will create a virtual presence sensor for each member of your circle.  I have created an Advanced Presence Sensor that can be used with Life360 and adds a network ping feature for redundancy.

    Hubitat Virtual Presence Driver
  7. First, go to the Drivers code and click New Driver.

    Hubitat Drivers Code
  8. Copy and paste the below code in to the new Driver window.  Then click Save and then Drivers code to exit the editor.

    /**
     *  Advanced Presence Sensor
     *
     *  MIT License
     *
     *  Copyright (c) 2023 This Old Smart Home
     *
     *  Permission is hereby granted, free of charge, to any person obtaining a copy
     *  of this software and associated documentation files (the "Software"), to deal
     *  in the Software without restriction, including without limitation the rights
     *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     *  copies of the Software, and to permit persons to whom the Software is
     *  furnished to do so, subject to the following conditions:
     *
     *  The above copyright notice and this permission notice shall be included in all
     *  copies or substantial portions of the Software.
     *
     *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     *  SOFTWARE.
     *
     */
    
    public static String version()          {  return "v3.0.1"  }
    public static String name()             {  return "Advanced Presence Sensor"  }
    public static String codeUrl()
    {
        return "https://raw.githubusercontent.com/TOSH-SmartHome/Hubitat-Advanced-Presence-Sensor/main/advance_presence_sensor.groovy"
    }
    public static String driverInfo()
    {
        return """
            <p style='text-align:center'></br>
            <strong><a href='https://thisoldsmarthome.com' target='_blank'>This Old Smart Home</a></strong> (TOSH-SmartHome)</br>
            ${name()}</br>
            <em>${version()}</em></p>
        """
    }
    
    metadata {
        definition (name: name(), namespace: "tosh", author: "John Goughenour", importUrl: codeUrl()) {
            capability "Configuration"
            capability "Refresh"
            capability "Sensor"
            capability "Presence Sensor"
            
            attribute "MqttConnection", "enum", ["CONNECTED", "DISCONNECTED", "UNKNOWN"]
            attribute "room", "enum", ["not_home", "garage"]
            attribute "pingable", "enum", ["yes", "no"]
    		
            command "arrived"
            command "departed"
        }   
        
        preferences {
            def poll = [:]
    		poll << [5 : "Every 5 seconds"]
    		poll << [10 : "Every 10 seconds"]
    		poll << [15 : "Every 15 seconds"]
    		poll << [30 : "Every 30 seconds"]
    		poll << [45 : "Every 45 seconds"]
    		poll << [60 : "Every 1 minute"]
    		poll << [120 : "Every 2 minutes"]
    		poll << [300 : "Every 5 minutes"]
    		poll << [600 : "Every 10 minutes"]
            
            input (name: "ipAddress", type: "string", title: "<b>Phone IP Address</b>", required: false, 
                   description: "Enter IP address to enable Wi-F- presence. </br>Timeout is required to change presence state.")
            input (name: "timeout", type: "number", title: "<b>Timeout</b>", range: "0..99", required: false, defaultValue: 0, 
                   description: "Number of tries without a response before device is not present. </br>Range: 0-99 Default: 0 (Disabled)")
            input (name: "heartbeat", type: "enum", title: "<b>Heartbeat</b>", options: poll, defaultValue: 60, 
                   description: "Set polling intervals from every 5 seconds to every 10 minutes </br><i>Default: </i><font color=\"red\">Every 1 minute</font>")
            input(name: "mqttBroker", type: "string", title: "<b>MQTT Broker</b>", description: "Enter MQTT Broker IP and Port e.g. server_IP:1883", required: false)
            input(name: "mqttUsername", type: "string", title: "<b>MQTT Username</b>", description: "Enter MQTT broker username", required: false)
            input(name: "mqttPassword", type: "password", title: "<b>MQTT Password</b>", description: "Enter password for your MQTT Broker.", required: false)
            input(name: "mqttTopic", type: "string", title: "<b>MQTT Topic</b>", description: "Enter MQTT Room Topic to listen for", required: false)
            input(name: "infoLogging", type: "bool", title: "<b>Enable Description Text</b>", defaultValue: "true", description: "", required: false)
            input(name: "debugLogging", type: "bool", title: "<b>Enable Debug Logging</b>", defaultValue: "true", description: "", required: false)
            input name:"about", type: "text", title: "<b>About Driver</b>", 
                description: "An advanced virtual presence sensor that uses MQTT to update additional attributes. ${driverInfo()}"
         }
    }
    
    def installed(){
    	if(infoLogging) log.info "${device.displayName} is installing"
        configure()
    }
    
    def updated(){
    	if(infoLogging) log.info "${device.displayName} is updating"
        configure()
    }
    
    def uninstalled(){
    	if(infoLogging) log.info "${device.displayName} is uninstalling"
    	unschedule()
        if(mqttTopic) 
            interfaces.mqtt.unsubscribe("${mqttTopic}")
        interfaces.mqtt.disconnect()
    }
    
    def configure() {
        if(infoLogging) log.info "${device.displayName} is configuring"
    	unschedule()
        if(mqttBroker && mqttUsername) {
            state.mqtt = true
            if(mqttTopic) 
                interfaces.mqtt.unsubscribe("${mqttTopic}")
            interfaces.mqtt.disconnect()
        } else state.mqtt = false
    
        if(infoLogging) log.info "${device.displayName} is initializing attributes"    
        // Initialize Attributes and Stat Variables
        sendEvent(name: "MqttConnection", value: 'DISCONNECTED')
        sendEvent(name: "room", value: 'not_home')
        sendEvent(name: "pingable", value: 'no')
        state.tryCount = 0   
    
        initialize()
    }
    
    def initialize() {
        if(infoLogging) log.info "${device.displayName} is initializing"
        
        // Setup MQTT Connection
        if(state.mqtt) mqtt_connect()
        
        if(heartbeat.toInteger() < 60) {
            cron = "0/$heartbeat * * * * ? *"
        } else {
            cron = "0 0/${heartbeat.toInteger() / 60} * * * ? *" 
        }
        schedule(cron, refresh)
        runIn(2, refresh)
    }
    
    def refresh() {
        if(infoLogging) log.info "${device.displayName} is refreshing"
        
        if( state.mqtt && !interfaces.mqtt.isConnected() ) mqtt_connect()
        
        if (ipAddress) {
            state.tryCount++
            if (timeout > 0 && state.tryCount > timeout) departed()
    	    asynchttpGet("httpGetCallback", [uri: "http://${ipAddress}/"])
        }
    }
    
    // handle commands
    def arrived() {
        if(infoLogging) log.info "${device.displayName} is present"
    	sendEvent(name: "presence", value: 'present')
        if(state.mqtt) sendMqttCommand("present", 'presence')
    }
    
    def departed() {
        if(infoLogging) log.info "${device.displayName} is not present"
    	if(device.currentValue("room") == 'not_home' && device.currentValue("pingable") == 'no') {
            sendEvent(name: "presence", value: 'not present')
            if(state.mqtt) sendMqttCommand("not present", 'presence')
        }
    }
    
    def change_rooms(room) {
        if(infoLogging) log.info "${device.displayName} is  changing rooms"
        sendEvent(name: "room", value: room)
        if(state.mqtt) sendMqttCommand(room, 'room')
        if(room != 'not_home' && device.currentValue("presence") == 'not present') arrived()
    }
    
    def mqtt_connect() {
        if(debugLogging) log.debug "${device.displayName} is to connect to MQTT Broker."
        
        try {
            if(debugLogging) log.debug "${device.displayName} settting up MQTT Broker"
            interfaces.mqtt.connect(
                "tcp://${mqttBroker}", 
                "${location.hub.name.toLowerCase().replaceAll(' ', '_')}_${device.getDeviceNetworkId()}", 
                mqttUsername, 
                mqttPassword
            )
                    
            if(mqttTopic) {
                if(debugLogging) log.debug "${device.displayName} subscribing to topic: ${mqttTopic}"
                interfaces.mqtt.subscribe("${mqttTopic}")
            }
        } catch(Exception e) {
            log.error "Unable to connect to the MQTT Broker ${e}"
            sendEvent(name: "MqttConnection", value: 'DISCONNECTED')
        }
    }
    
    def sendMqttCommand(cmnd, payload) {
        if(debugLogging) log.debug "${device.displayName} MQTT sending Command: ${cmnd} Payload: ${payload}"
        if(debugLogging) 
                log.debug "${device.displayName} is sending Topic: stat/${device.displayName.toLowerCase().replaceAll(' ', '_')}/${payload}/ Command: ${cmnd}"
            interfaces.mqtt.publish(
                "stat/${device.displayName.toLowerCase().replaceAll(' ', '_')}/${payload}/", 
                "${cmnd}", 2, true
            )
    }
    
    // parse events and messages
    def parse(message) {
        def msg = interfaces.mqtt.parseMessage(message)
    	if(debugLogging) log.debug "Parsing '${msg}'"
    	// TODO: handle 'switch' attribute
        switch(msg.topic) {
            case "${mqttTopic}":
               change_rooms(msg.payload)
               break
            default:
                if(debugLogging) log.info "Parsing ${device.displayName}: nothing to parse"
        }
    }
    
    def mqttClientStatus(status) {
        if(debugLogging) log.debug "MQTT Client Status: ${status}"
        switch(status) {
            case ~/.*Connection succeeded.*/:
                sendEvent(name: "MqttConnection", value: 'CONNECTED')
                break
            case ~/.*Error.*/:
                log.error "MQTT Client Status ${device.displayName}: has errored"
                sendEvent(name: "MqttConnection", value: 'DISCONNECTED')
                interfaces.mqtt.disconnect()
                runIn(5, mqtt_connect)
                break
            default:
                log.warn "MQTT Client Status ${device.displayName}: unknown status"
                sendEvent(name: "MqttConnection", value: 'UNKNOWN')
        }
    }
    
    def httpGetCallback(response, data) {
    	if(debugLogging) log.debug "${device.displayName}: httpGetCallback(${groovy.json.JsonOutput.toJson(response)}, data)"
    	
    	if (response && response.status == 408 && response.errorMessage.contains("Connection refused")) {
    		state.tryCount = 0
            sendEvent(name: "pingable", value: 'yes')
    		arrived()
    	} else sendEvent(name: "pingable", value: 'no')
    }

     

    Hubitat Advanced Presence Sensor Driver code
  9. In your Life360 device settings, change your device to the new Advanced Presence Sensor driver and click Save Device.

    Hubitat Life360 Device.
  10. Important: This will require you to set up an IP reservation or static IP for your phone.

    Scroll down to Preferences and do the following:

    1. Under Phone IP Address, enter your IP address.
    2. Set the Heartbeat to your desired interval.  The heartbeat is how often it will ping your phone.
    3. Click Save Preferences.

    You can use this with most apps that use the virtual presence driver or you can use it as a stand alone network ping driver.

    Hubitat Life360 Device Preferences.

Home Assistant

Life360

The Official Documentation for Life360

Setting Up the Automation

  1. First, go to Settings and Devices & Services.

    Home Assistant Settings Screen
  2. On the Integrations tab, click the Blue Add Integration button at the bottom of the screen.

    Home Assistant Settings Integration Screen
  3. You can search for Life360 and select it from the list.

    Home Assistant Select Integration Screen
  4. Enter your Life360 credentials and click Submit.

    Home Assistant Life360 Credentials
  5. Success: That’s it, Life360 installed. Click Finish

    Home Assistant Life360 Success