Here are a couple of my smart apps ported from smartthings garden hue and battery monitor

must have missed a line.

or more likely logging is for more detailed debugging if you turn it on and i need multiple log levels just to be able to get only that one line.

ya just looked that is by design the minimal logging as the field is really debugging and puts out lot of stuff.. i would have to revamp it all and add a debug level ie off minimal (the one line) full, and that will take a while to change everything.. will get to it when i have a chance.

1 Like
  • 4.6 change debug to log level, rewrite all logging, change many to info which didnt exist when this is written. levels are off, minimal, maximum, minimal corresponds to what
  • loging off previously was, maximum corresponds to what debugging on previously was.

i am sure i missed something as there were probably over 100 lines changed.. so let me know if you find something.

1 Like

No errors anyone everything looks ok? I tested a few paths, but depending on .options there are at least 10 different paths through the code.

I haven't had too much time to look at it. Will run it tonight and let you know...

1 Like

Thanks for this!

I'm struggling to get the settings tweaked exactly right to accomplish what I'm after. My goal is to get 3 colors (red, green, white) to cycle in order; preferably for a 5 second duration.

I find that if I turn holiday mode off, every color is cycled in order. Regardless if only 3 are turned on.

But if I turn on holiday mode, only my 3 selected colors are cycled, but random mode is forced.

Is it possible to get:

  1. Random mode = False - while only cycling my 3 specified colors in order?
  2. A 5 second duration option added to the menu?

Thank you!

No .. the are no settings for order.

OK. Can random mode be disabled?

That way if an order can't be specified . . . the selected colors always display in the same order?

Ill look at that.

1 Like

new version of garden hue:

did some work for my govee lights..

since they are true rgb added 5 totally random color options .. when any off these are hit in the random queue , it generates a random color with hue between 1 and 100.

also added 5 user define effects that can kick in.. you set the effects from the list of effects for your fixtures.. mine are:

  • scenes : [101=Aurora, 102=Cherry blossoms, 103=Star, 104=Rainbow, 105=Deep sea, 106=Desert, 107=Karst Cave, 108=Glacier, 109=Raindrop, 110=Night, 111=Electro Dance, 112=Stacking, 113=Poppin, 114=Dancing, 115=Swing, 116=Morning, 117=Illumination, 118=Bright, 119=Sports, 120=Siren, 121=Dreamland, 122=Mother's Day, 123=Christmas, 124=Candlelight, 125=Fireworks, 126=Party, 127=Carnival, 128=Halloween, 129=Halloween B, 130=Halloween C, 131=Halloween D, 132=Christmas Tree, 133=Sled, 134=Christmas Gift, 135=Cheerful, 136=Rush, 137=Flash, 138=Speeding, 139=Crossing, 140=Longing, 141=Warm, 142=Tension, 143=Heartbeat, 144=Nebula, 145=Moonlight, 1=Deep sea, 20=Spring, 21=Summer, 2=Longing, 3=Sunset glow, 4=Rainbow, 5=Fire, 7=Forest, 8=Flower Field]

warning: do not use fade in or fade out with govee lights.. as it overwhelms the hub. since there is no fade implemented directly in the bulbs.

  • v 4.7 warning about using fade with govee lights.. will overwhelm the hub. as there is no internal fade up or down fx.
  • add 5 new pure random colors 1-100 as govee and other lights are true 16million colors.
  • v 4.8 also for govee only add 5 user selectable effects into the mix.

new version is in my github.. search for garden hue

Gotta say, I'm still running Battery Monitor and just love it. :smiley:

1 Like

New version again in github and package manager to add a change random colors to generate both hue and saturation,

Also added 5 named color pulldowns from a standard list of colors thanks to @jvm33 for assistance (he coded the table of names to rgb mappings), that i borrowed.

Note: that many colors of these named colors do not work well with rgb lights so try them and see.

I like the BatteryMonitor app, run it every day. But I took the liberty of modifying the display so that all sections are in alphabetical order. Here's the revised code:

// lgk change all \n to \r\n so att mail to text does not reject the email
//
def smartAppNameFull() {
    return  "BatteryMonitor SmartApp for Hubitat"
}

def smartAppNameShort() {
    return  "BatteryMonitor"
}

def smartAppVersion() {
    return  "Version 1.1"
}

def smartAppAuthor() {
    return  "Author Brandon Gordon, larry kahn, John Land"
}

def smartAppCopyright() {
    return  "Copyright (c) 2014 Brandon Gordon, 2024 larry kah "
}

def smartAppSource() {
    return  "https://github.com/notoriousbdg/SmartThings.BatteryMonitor"
}

def smartAppDescription() {
    return  "This SmartApp helps you monitor the status of your SmartThings devices with batteries."
}

def smartAppRevision () {
    return  '2014-11-14  v0.0.1\r\n' +
            ' * Initial release\r\n' +
            '2014-11-15  v0.0.2\r\n' +
            ' * Moved status to main page\r\n' +
            ' * Removed status page\r\n' +
            ' * Improved formatting of status page\r\n' +
            ' * Added low, medium, high thresholds\r\n' +
            ' * Handle battery status strings of OK and Low\r\n\r\n' +
            '2014-11-15  v0.0.3\r\n' +
            ' * Added push notifications\r\n\r\n' +
            '2014-11-20  v0.0.4\r\n' +
            ' * Added error handling for batteries that return strings\r\n\r\n' +
            '2014-12-26  v0.0.5\r\n' +
            ' * Move app metadata to a new about page\r\n' +
            ' * Changed notifications to only send at specified time daily\r\n' +
            ' * modified by lgkahn to send complete message whenever the schedule fires, showing the status of all devices.\r\n\r\n' +
			'2025-10-16 v0.0.6\r\n' +
			' * modified by John Land & ChatGPT to sort each section alphabetically in ascending order.'
}

def smartAppLicense() {
    return  'Licensed under the Apache License, Version 2.0 (the "License"); you ' +
            'may not use this file except in compliance with the License. You ' +
            'may obtain a copy of the License at:' +
            '\r\n\r\n' +
            'http://www.apache.org/licenses/LICENSE-2.0' +
            '\r\n\r\n' +
            'Unless required by applicable law or agreed to in writing, software ' +
            'distributed under the License is distributed on an "AS IS" BASIS, ' +
            'WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or ' +
            'implied. See the License for the specific language governing ' +
            'permissions and limitations under the License.'
}

definition(
    name: "BatteryMonitor",
    namespace: "notoriousbdg",
    author: "Brandon Gordon",
    description: "SmartApp to monitor battery levels.",
    category: "Convenience",
    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")

preferences {
    page name:"pageStatus"
    page name:"pageConfigure"
    page name:"pageAbout"
}

// Show About Page
def pageAbout() {
    def pageProperties = [
        name:           "pageAbout",
        title:          smartAppNameFull(),
        nextPage:       "pageConfigure",
        uninstall:      true
    ]

    return dynamicPage(pageProperties) {
        section() {
            paragraph smartAppVersion() + "\r\n" +
                      smartAppAuthor() + "\r\n" +
                      smartAppCopyright()
        }
        
        section() {
            paragraph smartAppDescription()
        }
        
        section() {
            href(
                name: "sourceCode",
                title: "Source Code (Tap to view)",
                required: false,
                external: true,
                style: "external",
                url: smartAppSource(),
                description: smartAppSource()
            )
        }

        section() {
            paragraph title: "Revision History",
                      smartAppRevision()
        }
        
        section() {
            paragraph title: "License",
                      smartAppLicense()
        }  
    }
}

// Show Configure Page
def pageConfigure() {
    def helpPage =
        "Select devices with batteries that you wish to monitor."

    def inputBattery   = [
        name:           "devices",
        type:           "capability.battery",
        title:          "Which devices with batteries?",
        multiple:       true,
        required:       true
    ]

    def inputLevel1    = [
        name:           "level1",
        type:           "number",
        title:          "Low battery threshold?",
        defaultValue:   "20",
        required:       true
    ]

    def inputLevel3    = [
        name:           "level3",
        type:           "number",
        title:          "Medium battery threshold?",
        defaultValue:   "70",
        required:       true
    ]

    def inputTime      = [
        name:           "time",
        type:           "time",
        title:          "Notify at what time daily?",
        required:       true
    ]

    def nightlyStatusMsg = [
        name:           "nightlyStatus",
        type:           "bool",
        title:          "Send scheduled status message for all devices?",
        defaultValue:   true
    ]
    
    def inputPush      = [
        name:           "pushMessage",
        type:           "bool",
        title:          "Send push notifications?",
        defaultValue:   true
    ]

    def pageProperties = [
        name:           "pageConfigure",
        title:          smartAppNameShort() + " Configuration",
        nextPage:       "pageStatus",
        uninstall:      true
    ]

    return dynamicPage(pageProperties) {
        section("About") {
            paragraph helpPage
        }

        section("Devices") {
            input inputBattery
        }
        
        section("Settings") {
            input inputLevel1
            input inputLevel3
        }
        
        section("Notification") {
            input inputTime
            input inputPush   
            input "sendPushMessage", "capability.notification", title: "Notification Devices: Hubitat PhoneApp or Pushover", multiple: true, required: false
            input nightlyStatusMsg
        }

        section([title:"Options", mobileOnly:true]) {
            label title:"Assign a name", required:false
        }
    }
}

def installed() {
    log.debug "Initialized with settings: ${settings}"
    initialize()
}

def updated() {
    unschedule()
    unsubscribe()
    initialize()
}

def initialize() {
    schedule(settings.time, updateStatus)
}

def send(msg) {
    log.debug msg
    if (settings.pushMessage) {
        sendPushMessage.deviceNotification(msg)
    } 
}

// Show Status page
def pageStatus() {
    def pageProperties = [
        name:       "pageStatus",
        title:      smartAppNameShort() + " Status",
        nextPage:   null,
        install:    true,
        uninstall:  true
    ]

    if (settings.devices == null) {
        return pageAbout()
    }
   
    // Use lists instead of strings for sorting
    def listLevel0 = []
    def listLevel1 = []
    def listLevel2 = []
    def listLevel3 = []
    def listLevel4 = []

    if (settings.level1 == null) { settings.level1 = 33 }
    if (settings.level3 == null) { settings.level3 = 67 }
    if (settings.pushMessage) { settings.pushMessage = true }
    
    return dynamicPage(pageProperties) {
        settings.devices.each() {
            try {
                if (it.currentBattery == null) {
                    listLevel0 << it.displayName
                } else if (it.currentBattery >= 0 && it.currentBattery <  settings.level1.toInteger()) {
                    listLevel1 << "${it.currentBattery}  ${it.displayName}"
                } else if (it.currentBattery >= settings.level1.toInteger() && it.currentBattery <= settings.level3.toInteger()) {
                    listLevel2 << "${it.currentBattery}  ${it.displayName}"
                } else if (it.currentBattery >  settings.level3.toInteger() && it.currentBattery < 100) {
                    listLevel3 << "${it.currentBattery}  ${it.displayName}"
                } else if (it.currentBattery == 100) {
                    listLevel4 << it.displayName
                } else {
                    listLevel0 << "${it.currentBattery}  ${it.displayName}"
                }
            } catch (e) {
                log.trace "Caught error checking battery status."
                log.trace e
                listLevel0 << it.displayName
            }
        }

        if (listLevel0) {
            section("Batteries with errors or no status") {
                paragraph listLevel0.sort(false).join("\r\n")
            }
        }
        
        if (listLevel1) {
            section("Batteries with low charge (less than $settings.level1)") {
                paragraph listLevel1.sort(false).join("\r\n")
            }
        }

        if (listLevel2) {
            section("Batteries with medium charge (between $settings.level1 and $settings.level3)") {
                paragraph listLevel2.sort(false).join("\r\n")
            }
        }

        if (listLevel3) {
            section("Batteries with high charge (more than $settings.level3)") {
                paragraph listLevel3.sort(false).join("\r\n")
            }
        }

        if (listLevel4) {
            section("Batteries with full charge") {
                paragraph listLevel4.sort(false).join("\r\n")
            }
        }

        section("Menu") {
            href "pageStatus", title:"Refresh", description:""
            href "pageConfigure", title:"Configure", description:""
            href "pageAbout", title:"About", description: ""
        }
    }
}

// Nightly status report
def nightlyStatus() {
    def listLevel0 = []
    def listLevel1 = []
    def listLevel2 = []
    def listLevel3 = []
    def listLevel4 = []

    def myhub = location.hubs[0].name
    def outgoingMsg = "For Hub: $myhub\r\n"
    
    if (settings.level1 == null) { settings.level1 = 33 }
    if (settings.level3 == null) { settings.level3 = 67 }
    
    if (settings.nightlyStatus == true) {
        settings.devices.each() {
            try {
                if (it.currentBattery == null) {
                    listLevel0 << it.displayName
                } else if (it.currentBattery >= 0 && it.currentBattery < settings.level1.toInteger()) {
                    listLevel1 << "${it.currentBattery}  ${it.displayName}"
                } else if (it.currentBattery >= settings.level1.toInteger() && it.currentBattery <= settings.level3.toInteger()) {
                    listLevel2 << "${it.currentBattery}  ${it.displayName}"
                } else if (it.currentBattery > settings.level3.toInteger() && it.currentBattery < 100) {
                    listLevel3 << "${it.currentBattery}  ${it.displayName}"
                } else if (it.currentBattery == 100) {
                    listLevel4 << it.displayName
                } else {
                    listLevel0 << "${it.currentBattery}  ${it.displayName}"
                }
            } catch (e) {
                log.trace "Caught error checking battery status."
                log.trace e
                listLevel0 << it.displayName
            }
        }

        if (listLevel0) {
            outgoingMsg += "Batteries with errors or no status\r\n\r\n${listLevel0.sort(false).join('\r\n')}"
        }
       
        if (listLevel1) {
            outgoingMsg += "\r\n\r\nBatteries with low charge (less than $settings.level1)\r\n\r\n${listLevel1.sort(false).join('\r\n')}"
        }
    
        if (listLevel2) {
            outgoingMsg += "\r\n\r\nBatteries with medium charge (between $settings.level1 and $settings.level3)\r\n\r\n${listLevel2.sort(false).join('\r\n')}"
        }

        if (listLevel3) {
            outgoingMsg += "\r\n\r\nBatteries with high charge (more than $settings.level3)\r\n\r\n${listLevel3.sort(false).join('\r\n')}"
        }

        if (listLevel4) {
            outgoingMsg += "\r\n\r\nBatteries with full charge\r\n\r\n${listLevel4.sort(false).join('\r\n')}"
        }
      
        send(outgoingMsg)
    }
}

def updateStatus() {
    settings.devices.each() {
        try {
            if (it.currentBattery == null) {
                send("${it.displayName} battery is not reporting.")
            } else if (it.currentBattery > 100) {
                send("${it.displayName} battery is ${it.currentBattery}, which is over 100.")
            } else if (it.currentBattery < settings.level1) {
                send("${it.displayName} battery is ${it.currentBattery} (threshold ${settings.level1}.)")
            }
        } catch (e) {
            log.trace "Caught error checking battery status."
            log.trace e
            send("${it.displayName} battery reported a non-integer level.")
        }
    }
    nightlyStatus()
}

Nice addition, John, hopefully Larry can pick it up. :slight_smile:

What I think new users would LOVE would be a "Select all" option in the device selection drop-down. Choose all battery devices and remove the few you don't want monitored. Or could auto-select all devices w/batteries and allow users to unselect devices they don't want to monitor (usually a small amount).

John:

Oops...for me, only Batteries with Full Charge (last section) is properly alphabatized...all the other sections before it are not...some examples:

Replaced the original code w/yours and saved in Apps Code section, then Used Refresh in app to update (also used Update in device selection section). :man_shrugging:

To be clear, when I said "alphabetized", I was including the battery reading, so that for each battery level (e.g., "37"), that group is alphabetized.

2 Likes

I think that a "Select All" option would be great too, but I tried that with another app I wrote and was (ultimately) told by ChatGPT that it couldn't be done in Groovy for Hubitat (apparently a Hubitat API limitation), so I gave up.

1 Like

Ah, thanks, I didn't notice that the alphabetizing was within battery readings...thanks, that makes sense of course.

ill look at it when i get a chance should be possible need to put it in a list to sort in alpha order.. so not easy .. so you need to do that for each section number, then also for all

1 Like

I believe the code I posted above does all of that.

1 Like