Hubitat Release: LED Alert System

Description:

This custom Hubitat app is specifically designed to maximize the capabilities of the Inovelli Light Switch, particularly its unique LED strip feature. It provides intelligent and automated control of the LED strip based on door/window statuses, lock states, intrusion events, and seasonal considerations. Ideal for Inovelli switch users looking to integrate advanced home automation and security features.

Key Features:

  1. LED Strip as Status Indicator: Leverages the Inovelli Light Switch’s LED strip to indicate various home statuses, including security system arm/disarm and lock status, providing an intuitive and visual representation of the home’s security state.
  2. Responsive to Doors and Windows: The LED strip changes based on door and window sensor data, offering immediate visual feedback when doors or windows are opened or closed.
  3. Intrusion Detection Awareness: In case of a security breach, the app ensures that the LED strip remains illuminated or flashes, maintaining visibility and alertness during critical situations.
  4. Customizable “Summer Mode”: Acknowledging seasonal lifestyle changes, the app includes a “Summer Mode” for relaxed control. Between May 1st and September 30th, the app introduces a 1-hour delay in LED strip activation when a window is opened, adapting to longer daylight hours and casual summer living.
  5. User Preferences and Dynamic Adaptation: Users can easily enable “Summer Mode” and choose which sensors and switches the app controls.

CODE:

definition(
    name: "LED Alert System",
    namespace: "mopzero",
    author: "mopzero",
    description: "Controls lights based on HSM status, intrusion, door, and lock status.",
    category: "Safety & Security",
    iconUrl: "",
    iconX2Url: ""
)

preferences {
    section("HSM Monitor") {
        input "selectedLights", "capability.switch", multiple: true, required: true, title: "Armed Indicator"
        input "intrusionLight", "capability.switch", multiple: true, required: true, title: "Intrusion Indicator"
    }
    section("Lock Monitor") {
        input "doorUnlockedLight", "capability.switch", multiple: true, required: false, title: "Select Unlocked Door Indicator"
        input "selectedLocks", "capability.lock", multiple: true, required: false, title: "Select Locks"
    }
    section("Door Monitor") {
        input "doorContactLight", "capability.switch", multiple: true, required: false, title: "Select Door Contact Indicator"
        input "selectedDoorContacts", "capability.contactSensor", multiple: false, required: true, title: "Select Door Contact"
    }
    section("Window Monitor") {
        input "windowContactLight", "capability.switch", multiple: true, required: false, title: "Select Window Contact Indicator"
        input "selectedWindowContacts", "capability.contactSensor", multiple: true, required: false, title: "Select Window Contact"
        input "enableSummerMode", "bool", title: "Enable Summer Mode", required: false, defaultValue: false
        paragraph "Delay Window Monitor indicator 1 Hour between May 1st and September 30th."
    }
    section("Logging") {
         input "loggingEnabled", "bool", title: "Enable Logging", required: false, defaultValue: false
    }
}
String currentHsmStatus = "disarmed"
boolean intrusionDetected = false

def installed() {
    state.currentHsmStatus = "disarmed"
    intrusionDetected = false
    initialize()
}

def updated() {
    state.currentHsmStatus = "disarmed"
    intrusionDetected = false
    unsubscribe()
    initialize()
}

def initialize() {
    subscribe(location, "hsmStatus", "hsmStatusHandler")
    subscribe(location, "hsmAlert", "hsmAlertHandler")
    selectedLocks.each {
        subscribe(it, "lock", "lockStatusHandler")
    }
    selectedDoorContacts.each {
        subscribe(it, "contact", "doorContactHandler")
    }
    selectedWindowContacts.each {
        subscribe(it, "contact", "windowContactHandler")
    }    
}

def hsmStatusHandler(evt) {
    logDebug("HSM status is about to change from ${state.currentHsmStatus} to ${evt.value}")

    // Update the current HSM status
    state.currentHsmStatus = evt.value
    logDebug("HSM status changed to ${evt.value}")
    logDebug("currentHsmStatus updated to: ${state.currentHsmStatus}")

    if (isSystemArmed() && areAllLocksLocked()) {
        logDebug("System is armed and all locks are locked, turning on selected lights")
        selectedLights.each { it.on() }
    } else if (evt.value == "disarmed") {
        logDebug("System is disarmed, handling lights based on lock status")
        intrusionLight.off()
        selectedLights.each { it.off() } // Turning off the armed light

        if (areAnyLocksUnlocked()) {
            logDebug("Doors are unlocked, cycling unlock light")
            runIn(1, "cycleLight")
        } else {
            logDebug("All doors are locked, turning off unlock light")
            doorUnlockedLight.off()
        }
        
        state.intrusionDetected = false // Resetting intrusion detection
    }
}

def cycleLight(light) {
    light.off()
    runIn(1, { light.on() })
}


def hsmAlertHandler(evt) {
    logDebug("HSM alert: ${evt.value}")
    if (evt.value == "intrusion-home") {
        state.intrusionDetected = true
        logDebug("Intrusion detected, setting state.intrusionDetected flag")
        intrusionLight.on()
    }
}

def lockStatusHandler(evt) {
    logDebug("Lock status changed to ${evt.value} for device ${evt.device.displayName}")
    if (selectedLocks && doorUnlockedLight) {
        if (evt.value == "unlocked") {
            if (!state.intrusionDetected && !isAnyDoorOpen()) {
                logDebug("Door unlocked, no intrusion detected, and no doors are open. Turning on doorUnlockedLight")
                doorUnlockedLight.on()
            } else {
                logDebug("Door unlocked but either intrusion detected or a door is open. Not turning on doorUnlockedLight")
            }
        } else if (evt.value == "locked") {
            logDebug("Door locked, evaluating conditions")

            if (!state.intrusionDetected && areAllLocksLocked()) {
                logDebug("No intrusion detected and all doors are locked, turning off doorUnlockedLight")
                doorUnlockedLight.off()
            } else {
                logDebug("Either intrusion is detected or not all doors are locked, keeping doorUnlockedLight on")
            }
    
            if (isSystemArmed() && !state.intrusionDetected && areAllLocksLocked()) {
                logDebug("System is armed, no intrusion detected, and all locks are locked, cycling armed lights")
                   selectedLights.each { it.off() }
                runIn(1, "turnArmedLightsOn")
            } else {
                logDebug("Conditions not met for cycling armed lights")
            }

            if (isAnyWindowOpen()) {
                windowContactLight.off()
                runIn(1, "turnOnWindowContactLight")
                logDebug("Lock locked and window is open. Cycling window open light.")
            }
        }
    }
}

def doorContactHandler(evt) {
    if (selectedDoorContacts && doorContactLight) {
        logDebug("Door contact event: ${evt.displayName} is ${evt.value}")

        // If there's an intrusion detected, do not change the state of the door contact light.
        if (state.intrusionDetected) {
            logDebug("Intrusion detected. Not changing door contact light.")
            return
        }

        if (evt.value == "closed") {
            doorContactLight.off()
            logDebug("Door closed. Turning off door contact light.")
        } else if (evt.value == "open") {
            doorContactLight.on()
            logDebug("Door opened. Turning on door contact light.")
        }
    } else {
        logDebug("doorContactLight is not configured in preferences.")
    }
}

def windowContactHandler(evt) {
    if (windowContactLight && selectedWindowContacts) {
        logDebug("Window contact event: ${evt.displayName} is ${evt.value}")

        if (evt.value == "open") {
            if (enableSummerMode && isInSummerPeriod()) {
                logDebug("Summer Mode active and within summer period. Delaying window open light activation by 1 hour.")
                runIn(3600, "turnOnWindowContactLight") // Delay for 1 hour
            } else {
                windowContactLight.on()
                logDebug("Window opened. Turning on window contact light immediately.")
            }
        } else if (evt.value == "closed") {
            // Cancel any scheduled job for turning on the light
            unschedule("turnOnWindowContactLight")
            windowContactLight.off()
            logDebug("Window closed. Turning off window contact light immediately and canceling any delayed activation.")

            if (allDoorsAndWindowsClosed()) {
                logDebug("All doors and windows are closed.")
            } else {
                logDebug("Some doors or windows are still open.")
            }
        }
    }
}

def turnArmedLightsOn() {
    selectedLights.each { it.on() }
}

def turnOnDoorUnlockedLight() {
    doorUnlockedLight.on()
}

def turnOnWindowContactLight() {
    windowContactLight.on()
    logDebug("Window contact light turned on.")
}

def isSystemArmed() {
    boolean armed = state.currentHsmStatus in ["armedAway", "armedHome", "armedNight"]
    logDebug("Checking if system is armed. currentHsmStatus: ${state.currentHsmStatus}, isArmed: ${armed}")
    return armed
}

def isInSummerPeriod() {
    def currentDate = new Date()
    def calendar = Calendar.instance
    calendar.time = currentDate
    def currentYear = calendar.get(Calendar.YEAR)

    def startSummer = Date.parse("yyyy-MM-dd", "${currentYear}-05-01")
    def endSummer = Date.parse("yyyy-MM-dd", "${currentYear}-09-30")

    return currentDate.after(startSummer) && currentDate.before(endSummer)
}
def allDoorsAndWindowsClosed() {
    selectedDoorContacts.every { it.currentValue("contact") == "closed" } &&
    selectedWindowContacts.every { it.currentValue("contact") == "closed" }
}

def isAnyWindowOpen() {
    selectedWindowContacts.any { it.currentValue("contact") == "open" }
}

def areAllLocksLocked() {
    selectedLocks.every { it.currentValue("lock") == "locked" }
}

def areAnyLocksUnlocked() {
    selectedLocks.any { lock ->
        lock.currentValue("lock") == "unlocked" }
}

def isAnyDoorOpen() {
    selectedDoorContacts.any { it.currentValue("contact") == "open" }
}

def logDebug(String message) {
    if (loggingEnabled) { // Ensure 'loggingEnabled' is a boolean variable that controls logging
        log.debug message
    }
}

How I use the App:
I created 4 groups for the Inovelli Red series switches. Group one will blink red, group 2 will blink yellow, group 3 is pink, and group 4 is green. I also created rule machine rules that will interact with my blue series switches, sending the matching command to follow the associated group.

When the system is armed. This app turns on the Green group. If a door is unlocked, it turns on the yellow light. If the door is relocked with no intrustion, the light turns back to green. When an intrusion is detected, it turns on the red group and will ignore all other input. Doors and windows function much the same way. Due to how these switches handle on and off commands, there is some logic built into the app to cycle the previous state off then on.

This is my first attempt at writing a Hubitat App so feedback is greatly appreciated.

1 Like