[RELEASE] Advanced Mode Manager - Commercial-Grade Mode Engine & Transition Timers

If you've been looking for a way to easily manage what your house does when modes change, or need a reliable way to automate delays between modes (with safety checks!), this app brings it all into one dashboard.

Key Features:

  • Live Mode Dashboard: See your current mode, engine status, and a live countdown for any pending mode transitions right from the main page. It also features a built-in action history log.
  • Instant Device Enforcement: Up to 8 rules to instantly turn on/off switches, lock doors, and close garages the moment a specific mode activates.
  • Mode-to-Mode Transition Timers: Set up to 8 automated delays. For example, trigger "Arrival" mode, wait 15 minutes, and then automatically transition to "Home".
  • Presence Tethering: Tie a transition timer to a presence sensor. If the person leaves while the timer is counting down, the app intercepts the transition and forces a fallback mode.
  • Condition Gates: Prevent a mode transition from happening if someone is still moving around (Motion active) or if an appliance is running (Power > Threshold).
  • Sweep Mode: A manual override button to instantly enforce all device rules for whatever mode your hub is currently in.
  • Smart Aborts: If the hub's mode changes manually during a timer countdown, the app automatically aborts the pending transition to prevent conflicts.

raw.githubusercontent.com/ShaneAllen334/Hubitat_Apps/refs/heads/main/Advanced_Mode_Manager/Advanced_Mode_Manager.groovy

:handshake: Open Source & Free to Use

This project is completely free, open-source code. I know every smart home is unique, so please feel free to use it, tear it apart, edit the math, or adapt any part of the logic to perfectly fit your own setup. If you find a way to improve it, let me know!

I will say it is interesting to see how you have used apps for your automations. it is a much different approach than I have taken.

I divide my house into three manageable areas, Front, Back and Basement. I have three main "scene controllers" for those areas. Those apps use scenes (I don't use modes) that are stored in the child data driver device for each app. Each room has its own scenes, and the controller manages all room scenes based on motion, time, lux or a manual dashboard scene change. I do use a separate illuminance app and driver, that feeds the area apps for scene changes that depend on lux, so I can simply subscribe to a lux attribute flag changing true/false (such as Sunset, LowLight, and Day attributes).

Each area contains the rooms, with Front door being a room for the Front Controller, and back Back door also being a room in the Back controller. So outside lighting/security is co-mingled and controlled the same way as any other rooms in the area scene controllers.

The apps set any schedules for the scene changes, and subscribe to the sensors. The lighting scenes are stored in a map, which makes it really easy to iterate over the map devices to activate a scene, using data in the map for switch and level for each device. I just use a preference for a delay between commands, to not overload the mesh. I use a 200ms delay between commands.

All lighting timeout settings are stored in the data driver, and the data driver changes the motion attribute for each room to Active, Inactive, or Timeout, based on the parent controller sending the sensor state to activate it. That child device then calls the app parent methods to have the app change the scenes as needed. Scenes do not turn on or off themselves, but the Timeout state for a room set in the driver will cause the "All Off" scene to run when it calls the parent with the room timeout. "All Off" is a scene that a room is never actually "set" to, but it is a scene in my map and it gets executed like any other scene, when motion times out.

When the current scenes are set by the the parent based on time or lux, it also sets a "scheduledScene". That way when a scene is changed manually for any reason, it is easy to go back to the scheduled scene that should be running, if needed.

Mailbox sensor logic is just part of my Front Scene Controller, since it is at the front of the house. My Dog Watch sensor logic for dogs at the back door is part of the Back Scene Controller, instead of having separate apps for functions like that.

I use one virtual dimmer for all Alexa voice command triggers, and each app subscribes to that dimmer, but each only reacts to dimmer values that trigger methods in the proper apps based on the voice command routine used in Alexa.

Anyway, not to go into too much detail, but I'm just pointing out that I don't separate out apps by function like the apps you have been posting. I do it it by area, which works very well for me, but it also makes my scene apps very specific to my rooms and devices, so the apps and individual functions are not really shareable.

I use the device setting name in the maps as the the device name, so it is easy to take a device name from the map and get the device wrapper object for it to send commands to it.

Scene Map Method
// Scene Data Maps.  the Map data is returned when this method is called from executeScene()
Map getSceneMap(room) {

    if (room == "livingRoom") {
    return [
        "Dawn":[
            0:[d:"bottleLamp",s:"off",l:"NA"],
            1:[d:"ceramicLamp",s:"on",l:"10"],
            2:[d:"entryStairs",s:"off",l:"NA"],
            3:[d:"livingRoomCeiling",s:"on",l:"20"],
            4:[d:"standingLamp",s:"on",l:"lLL"],
            5:[d:"curveLamp",s:"on",l:"lLL"],
            6:[d:"treeLamp",s:"off",l:"NA"]
            ],
        "Morning":[
            0:[d:"bottleLamp",s:"off",l:"NA"],
            1:[d:"ceramicLamp",s:"on",l:"25"],
            2:[d:"entryStairs",s:"off",l:"NA"],
            3:[d:"livingRoomCeiling",s:"on",l:"20"],
            4:[d:"standingLamp",s:"on",l:"lLL"],
            5:[d:"curveLamp",s:"on",l:"lLL"],
            6:[d:"treeLamp",s:"on",l:"50"]
            ],
        "Day":[
            0:[d:"bottleLamp",s:"off",l:"NA"],
            1:[d:"ceramicLamp",s:"on",l:"mLL"],
            2:[d:"entryStairs",s:"off",l:"NA"],
            3:[d:"livingRoomCeiling",s:"on",l:"lLL-10"],
            4:[d:"standingLamp",s:"on",l:"mLL"],
            5:[d:"curveLamp",s:"on",l:"mLL"],
            6:[d:"treeLamp",s:"on",l:"50"]
            ],
        "Late Evening":[
            0:[d:"bottleLamp",s:"on",l:"mLL"],
            1:[d:"ceramicLamp",s:"on",l:"75"],
            2:[d:"entryStairs",s:"off",l:"NA"],
            3:[d:"livingRoomCeiling",s:"on",l:"20"],
            4:[d:"standingLamp",s:"on",l:"mLL"],
            5:[d:"curveLamp",s:"on",l:"mLL"],
            6:[d:"treeLamp",s:"off",l:"NA"]
            ],
        "Dinner":[
            0:[d:"bottleLamp",s:"on",l:"mLL"],
            1:[d:"ceramicLamp",s:"on",l:"90"],
            2:[d:"entryStairs",s:"on",l:"15"],
            3:[d:"livingRoomCeiling",s:"on",l:"20"],
            4:[d:"standingLamp",s:"on",l:"65"],
            5:[d:"curveLamp",s:"on",l:"65"],
            6:[d:"treeLamp",s:"on",l:"lLL"]
            ],
        "Evening":[
            0:[d:"bottleLamp",s:"on",l:"mLL"],
            1:[d:"ceramicLamp",s:"on",l:"50"],
            2:[d:"entryStairs",s:"off",l:"NA"],
            3:[d:"livingRoomCeiling",s:"on",l:"20"],
            4:[d:"standingLamp",s:"on",l:"mLL"],
            5:[d:"curveLamp",s:"on",l:"mLL"],
            6:[d:"treeLamp",s:"on",l:"lLL"]
            ],
        "Cooking":[
            0:[d:"bottleLamp",s:"on",l:"mLL"],
            1:[d:"ceramicLamp",s:"on",l:"50"],
            2:[d:"entryStairs",s:"off",l:"NA"],
            3:[d:"livingRoomCeiling",s:"on",l:"20"],
            4:[d:"standingLamp",s:"on",l:"mLL"],
            5:[d:"curveLamp",s:"on",l:"mLL"],
            6:[d:"treeLamp",s:"on",l:"lLL"]
            ],            
        "TV Time":[
            0:[d:"bottleLamp",s:"on",l:"25"],
            1:[d:"ceramicLamp",s:"on",l:"25"],
            2:[d:"entryStairs",s:"off",l:"NA"],
            3:[d:"livingRoomCeiling",s:"on",l:"25"],
            4:[d:"standingLamp",s:"on",l:"lLL"],
            5:[d:"curveLamp",s:"on",l:"lLL"],
            6:[d:"treeLamp",s:"on",l:"25"]
            ],
        "Dim":[
            0:[d:"bottleLamp",s:"off",l:"NA"],
            1:[d:"ceramicLamp",s:"on",l:"5"],
            2:[d:"entryStairs",s:"off",l:"NA"],
            3:[d:"livingRoomCeiling",s:"on",l:"20"],
            4:[d:"standingLamp",s:"on",l:"lLL"],
            5:[d:"curveLamp",s:"on",l:"lLL"],
            6:[d:"treeLamp",s:"off",l:"NA"]
            ],
        "Bright":[
            0:[d:"bottleLamp",s:"on",l:"mLL"],
            1:[d:"ceramicLamp",s:"on",l:"100"],
            2:[d:"entryStairs",s:"on",l:"20"],
            3:[d:"livingRoomCeiling",s:"on",l:"hLL"],
            4:[d:"standingLamp",s:"on",l:"hLL"],
            5:[d:"curveLamp",s:"on",l:"hLL"],
            6:[d:"treeLamp",s:"on",l:"mLL"]
            ],
        "Lamps Off":[
            0:[d:"bottleLamp",s:"off",l:"NA"],
            1:[d:"ceramicLamp",s:"off",l:"0"],
            2:[d:"entryStairs",s:"off",l:"NA"],
            3:[d:"livingRoomCeiling",s:"on",l:"20"],
            4:[d:"standingLamp",s:"off",l:"NA"],
            5:[d:"curveLamp",s:"off",l:"NA"],
            6:[d:"treeLamp",s:"off",l:"NA"]
            ],
        "All Off":[
            0:[d:"bottleLamp",s:"off",l:"NA"],
            1:[d:"ceramicLamp",s:"off",l:"0"],
            2:[d:"entryStairs",s:"off",l:"NA"],
            3:[d:"livingRoomCeiling",s:"off",l:"NA"],
            4:[d:"standingLamp",s:"off",l:"NA"],
            5:[d:"curveLamp",s:"off",l:"NA"],
            6:[d:"treeLamp",s:"off",l:"0"]
            ],
        "Night":[
            0:[d:"bottleLamp",s:"off",l:"NA"],
            1:[d:"ceramicLamp",s:"off",l:"NA"],
            2:[d:"entryStairs",s:"off",l:"NA"],
            3:[d:"livingRoomCeiling",s:"off",l:"NA"],
            4:[d:"standingLamp",s:"off",l:"NA"],
            5:[d:"curveLamp",s:"off",l:"NA"],
            6:[d:"treeLamp",s:"off",l:"NA"]
            ]
        ]
    }

    if (room == "kitchen") {
        
    return [
        "Dawn":[
            0:[d:"cornerLamp",s:"on",l:"lLL"],
            1:[d:"fanLight",s:"off",l:"NA"],
            2:[d:"islandLights",s:"on",l:"lLL"],
            3:[d:"kitchenCeiling",s:"on",l:"20"],
            4:[d:"underCabLights",s:"on",l:"NA"],
            5:[d:"kitchenSinkLight",s:"on",l:"20"]
            ],
        "Morning":
            [0:[d:"cornerLamp",s:"on",l:"lLL"],
            1:[d:"fanLight",s:"off",l:"NA"],
            2:[d:"islandLights",s:"on",l:"mLL+15"],
            3:[d:"kitchenCeiling",s:"on",l:"20"],
            4:[d:"underCabLights",s:"on",l:"NA"],
            5:[d:"kitchenSinkLight",s:"on",l:"25"]
            ],
        "Day":[
            0:[d:"cornerLamp",s:"on",l:"mLL"],
            1:[d:"fanLight",s:"off",l:"NA"],
            2:[d:"islandLights",s:"on",l:"mLL+15"],
            3:[d:"kitchenCeiling",s:"on",l:"20"],
            4:[d:"underCabLights",s:"on",l:"NA"],
            5:[d:"kitchenSinkLight",s:"on",l:"25"]
            ],
        "Late Evening":[
            0:[d:"cornerLamp",s:"on",l:"mLL"],
            1:[d:"fanLight",s:"off",l:"NA"],
            2:[d:"islandLights",s:"on",l:"mLL"],
            3:[d:"kitchenCeiling",s:"on",l:"20"],
            4:[d:"underCabLights",s:"on",l:"NA"],
            5:[d:"kitchenSinkLight",s:"on",l:"35"]
            ], 
        "Evening":[
            0:[d:"cornerLamp",s:"on",l:"mLL"],
            1:[d:"fanLight",s:"off",l:"NA"],
            2:[d:"islandLights",s:"on",l:"hLL"],
            3:[d:"kitchenCeiling",s:"on",l:"20"],
            4:[d:"underCabLights",s:"on",l:"NA"],
            5:[d:"kitchenSinkLight",s:"on",l:"55"]
            ], 
        "Cooking":[
            0:[d:"cornerLamp",s:"on",l:"mLL"],
            1:[d:"fanLight",s:"off",l:"NA"],
            2:[d:"islandLights",s:"on",l:"80"],
            3:[d:"kitchenCeiling",s:"on",l:"20"],
            4:[d:"underCabLights",s:"on",l:"NA"],
            5:[d:"kitchenSinkLight",s:"on",l:"65"]
            ],             
        "Dinner":[
            0:[d:"cornerLamp",s:"on",l:"hLL+20"],
            1:[d:"fanLight",s:"on",l:"18"],
            2:[d:"islandLights",s:"on",l:"80"],
            3:[d:"kitchenCeiling",s:"on",l:"20"],
            4:[d:"underCabLights",s:"on",l:"NA"],
            5:[d:"kitchenSinkLight",s:"on",l:"80"]
            ], 
        "TV Time":[
            0:[d:"cornerLamp",s:"on",l:"lLL"],
            1:[d:"fanLight",s:"off",l:"NA"],
            2:[d:"islandLights",s:"on",l:"lLL"],
            3:[d:"kitchenCeiling",s:"on",l:"20"],
            4:[d:"underCabLights",s:"on",l:"NA"],
            5:[d:"kitchenSinkLight",s:"on",l:"15"]
            ], 
        "Night":[
            0:[d:"cornerLamp",s:"off",l:"NA"],
            1:[d:"fanLight",s:"off",l:"NA"],
            2:[d:"islandLights",s:"off",l:"NA"],
            3:[d:"kitchenCeiling",s:"off",l:"NA"],
            4:[d:"underCabLights",s:"off",l:"NA"],
            5:[d:"kitchenSinkLight",s:"off",l:"NA"]
            ], 
        "All Off":[
            0:[d:"cornerLamp",s:"off",l:"NA"],
            1:[d:"fanLight",s:"off",l:"NA"],
            2:[d:"islandLights",s:"off",l:"NA"],
            3:[d:"kitchenCeiling",s:"off",l:"NA"],
            4:[d:"underCabLights",s:"off",l:"NA"],
            5:[d:"kitchenSinkLight",s:"off",l:"0"]
            ], 
        "Lamps Off":[
            0:[d:"cornerLamp",s:"off",l:"NA"],
            1:[d:"fanLight",s:"off",l:"NA"],
            2:[d:"islandLights",s:"off",l:"NA"],
            3:[d:"kitchenCeiling",s:"NA",l:"NA"],
            4:[d:"underCabLights",s:"off",l:"NA"],
            5:[d:"kitchenSinkLight",s:"off",l:"NA"]
            ], 
        "Bright":[
            0:[d:"cornerLamp",s:"on",l:"hLL"],
            1:[d:"fanLight",s:"on",l:"mLL"],
            2:[d:"islandLights",s:"on",l:"hLL"],
            3:[d:"kitchenCeiling",s:"on",l:"20"],
            4:[d:"underCabLights",s:"on",l:"NA"],
            5:[d:"kitchenSinkLight",s:"on",l:"90"]
            ], 
        "Dim":[
            0:[d:"cornerLamp",s:"on",l:"lLL"],
            1:[d:"fanLight",s:"off",l:"NA"],
            2:[d:"islandLights",s:"on",l:"lLL"],
            3:[d:"kitchenCeiling",s:"off",l:"NA"],
            4:[d:"underCabLights",s:"off",l:"NA"],
            5:[d:"kitchenSinkLight",s:"on",l:"5"]
            ]       
        ]
    }
}
Execute Map Scene
        Map sceneMap = getSceneMap(room)                                    // get the scenes map for room
        def devices = sceneMap."${scene}".collect{entry -> entry.value}     // get a map of device commands for the current room scene from the sceneMap

// Iterate through the devices map, get the device from room scene devices list, and send the device object and device parameters to commandDevice
        devices.each{device -> 
            if (!(device.s == "NA" && device.l == "NA")) {
                def thisDevice = settings?."${device.d}"  // get the device wrapper object from settings
                commandDevice(thisDevice, device.s, device.l, room, device.d)} 
                pauseExecution(exDelay)
            }

Then the commandDevice method can just use the device wrapper to send the command.
thisDevice.setLevel(lvl,3)

I never thought about zoning out sections of the house and in your case using scenes. This is an interesting approach but it makes it very custom to your use case. I think for me being in the Commercial HVAC Controls industry and the standards on how we are supposed to program equipment like chiller plants. I like to break each function out. Like Condenser Water Pumps, Secondary Pumps, Water Flow and Valve logic. For me it makes it easier to dive into the problem area.

I love how everyone can come up with their own way of attacking a problem and making automation work for them.

1 Like

That makes sense, there are certainly advantages to separating things out. My heating is controlled separately from the scene executers. For that I still have an app for each heating zone, and I have the controller apps controlling the zone valves directly (using Zigbee relays), with each controller having a virtual thermostat device, as well as a physical thermostat. I use my Honeywell T6 thermostats as control panels only, and I let the virtual thermostat actually control the zone valves and regulate the heat. I spent a bunch of time writing an anticipating cycling virtual thermostat driver, as the T6s were just horrible for me to keep a consistent temperature. I have my temp swings down to about half of a degree now, buy using anticipation based on temp rise/fall, with anticipation setpoints, and timed heat/cool cycles followed by a wait period to see how much a cycle has moved the the temperature, followed by another cycle if needed, until temp is going in the right direction.

What type of heating system do you have that controls valves per room (area). You must live up north?

Yes, in Vermont. It was a cold one this year :cold_face:

Front Zone: Kitchen, Living Room, Dining, Main Hall Bathroom, and 2nd Bedroom.
Main Bedroom: Main bedroom and en suite bathroom, and laundry room
Basement: Theater and Office

I added the Main Bedroom zone myself when doing a whole house renovation a few years ago, it was just easier to include the laundry room in that zone the way the pipes were already run. I also tore down and rebuilt the whole front section of the house during that reno, and I was living in the back area with the hallway to the front area boarded up for over a month in November. All the heating lines had to be cut out of the front of the house, so I really had to add a zone to the back section of house to get though that month. I put a portable stove in the laundry room, mini fridges in the extra bedroom, and the coffee maker in the hall bathroom. Cheaper than moving out when you lose your kitchen for a month, but it wasn't fun.

Bug fixes for Advanced Mode Manager are available on Github.