Skip to content

Instantly share code, notes, and snippets.

@chutch3
Last active June 29, 2025 14:37
Show Gist options
  • Save chutch3/2662014539066e1219280678ec9d5169 to your computer and use it in GitHub Desktop.
Save chutch3/2662014539066e1219280678ec9d5169 to your computer and use it in GitHub Desktop.
A Hubitat app that automatically refreshes the status of selected Z-Wave devices on a user-defined schedule. Ideal for users who want to keep device states up-to-date and avoid stale readings. Supports both simple interval selection and advanced cron expressions.
definition(
name: "Device Refresher",
namespace: "chutchens",
author: "Cody Hutchens",
description: "Refresh Z-Wave Devices on a schedule",
category: "Convenience",
iconUrl: "https://cdn-icons-png.flaticon.com/512/1828/1828817.png",
iconX2Url: "https://cdn-icons-png.flaticon.com/512/1828/1828817.png"
)
def commonIntervals = [
"Every 5 minutes": "0 */5 * * * ?",
"Every 15 minutes": "0 */15 * * * ?",
"Every hour": "0 0 * * * ?",
"Every day": "0 0 0 * * ?"
]
preferences {
section("Select devices to refresh:") {
input(
name: "refreshDevices",
type: "capability.refresh",
title: "Which devices?",
multiple: true,
required: true
)
}
section("Refresh schedule") {
input(
name: "scheduleType",
type: "enum",
title: "How often?",
options: commonIntervals.keySet() + ["Other..."],
defaultValue: "Every hour",
required: true
)
if (settings?.scheduleType == "Other...") {
input(
name: "customCron",
type: "text",
title: "Custom cron expression",
description: "e.g., 0 * * * * ? for every hour. See: https://crontab.guru/",
required: true
)
}
}
section("Battery Threshold") {
input(
name: "batteryThreshold",
type: "number",
title: "Battery threshold?",
description: "This is the threshold used to determine whether a refresh should be skipped due to low battery.",
defaultValue: 10,
required: true
)
}
section("Notifications (optional)") {
input(
name: "notificationDevice",
type: "capability.notification",
title: "Send notifications to (optional)",
required: false
)
}
}
def installed() {
log.debug "installed()"
updated()
}
def uninstalled() {
unschedule()
}
def updated() {
def commonIntervals = [
"Every 5 minutes": "0 */5 * * * ?",
"Every 15 minutes": "0 */15 * * * ?",
"Every hour": "0 0 * * * ?",
"Every day": "0 0 0 * * ?"
]
unschedule()
def cronExpr
if (settings.scheduleType == "Other...") {
cronExpr = settings.customCron
} else {
cronExpr = commonIntervals[settings.scheduleType]
}
try {
schedule(cronExpr, refreshHandler)
log.info "Scheduled refresh with cron: ${cronExpr}"
} catch (e) {
log.error "Invalid cron expression: ${cronExpr}"
if (settings.notificationDevice) {
settings.notificationDevice.deviceNotification("Device Refresher: Invalid cron expression: ${cronExpr}. Please check and correct it.")
}
}
}
def logForDevice(device, message, attributes = [], warn = false) {
def meta = attributes.collect { attr ->
def value = device.currentValue(attr)
"${attr}: ${value != null ? value : 'unknown'}"
}.join(", ")
def name = device?.displayName ?: "Unknown Device"
def level = warn ? "warn" : "info"
log."${level}"("${name}: ${message}${meta ? " | " + meta : ""}")
}
def refreshHandler() {
def batteryThreshold = settings.batteryThreshold ?: 10
refreshDevices.each { device ->
def power = device.currentValue("powerSource")
logForDevice(device, "Detected")
if (power == "battery") {
def battery = device.currentValue("battery")
if (battery != null && battery <= batteryThreshold) {
logForDevice(device, "Battery low, skipping refresh", ["powerSource", "battery"], true)
if (settings.notificationDevice) {
settings.notificationDevice.deviceNotification("Device Refresher: ${device.displayName} battery is low (${battery}%) — skipping refresh.")
}
return // skip this device
}
}
if (device.hasCommand("refresh")) {
logForDevice(device, "Refreshing device")
device.refresh()
} else {
logForDevice(device, "Device does not support refresh", warn=true)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment