[ re-release ] hub auto rebooter - max

Updated Dominick Meglio's app using AI ..
To do weekly-biweekly-monthly reboots. with lots of added logging.
I think It mostly works .. but I have not tested the hub security reboot.
( I do not use hub security ) < If someone want to test this that would be great.

Install ..

  1. Copy and Add code to new ( Apps Code )
  2. Add new + Add User-App in ( APPS )
  3. Setup using new App.
/**
 *
 *  Rebooter-MAX
 *  Hubitat SmartApp for Scheduled Rebooting/Restarting
 *
 *  Copyright 2019-2020 Dominick Meglio
 *  Updated 2025 - Warlock Weary
 *  v 2023.10.27 - Final version with native scheduling and logging
 *  v 2025.02.02 - Rebooter-MAX with Weekly - bi-weekly - monthly scheduling with added logging
 */

definition(
    name: "Rebooter-MAX",
    namespace: "dcm.rebooter-MAX",
    author: "Dominick Meglio updated by Warlock Weary",
    description: "Restart your hub on a weekly or bi-weekly or monthly schedule",
    category: "My Apps",
    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
    iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
    documentationLink: "https://github.com/dcmeglio/hubitat-rebooter/blob/master/README.md")

preferences {
    page(name: "prefMain")
}

def installed() {
    log.info "Rebooter-MAX Installed"
    initialize()
}

def updated() {
    log.info "Rebooter-MAX Updated With New Configuration"
    unschedule()
    unsubscribe()
    initialize()
}

def initialize() {
    logConfiguration()

    if (rebootType == "Time") {
        def time = timeToday(rebootTime, location.timeZone)
        def days = rebootDays.collect { it.substring(0, 3).toUpperCase() }.join(",")
        unschedule(executeReboot) // Clear existing schedules
        if (rebootFrequency == "Weekly") {
            // Schedule weekly reboot on the selected day
            def dayOfWeek = rebootDays[0].substring(0, 3).toUpperCase() // Get the first selected day
            def cronExpression = "00 ${time.minutes} ${time.hours} ? * ${dayOfWeek}*"
            schedule(cronExpression, executeReboot)
        } else if (rebootFrequency == "Bi-Weekly") {
            // Schedule bi-weekly reboot on the selected day
            def cronExpression = "00 ${time.minutes} ${time.hours} ? * ${days}*"
            schedule(cronExpression, executeReboot)
            // Schedule every other week
            schedule("00 ${time.minutes} ${time.hours} ? * ${days}#3", executeReboot) 
        } else if (rebootFrequency == "Monthly") {
            // Schedule monthly reboot on the selected day
            unschedule(executeReboot)
            schedule("00 ${time.minutes} ${time.hours} ${rebootDayOfMonth} * ?", executeReboot)
        }

        logSchedule(time)
    } else if (rebootType == "Button") {
        subscribe(rebootButton, "pushed.1", buttonPushed)
        log.info "Configured to reboot when ${rebootButton.displayName} is pressed"
    }
}

def buttonPushed(evt) {
    log.info "Button pressed - initiating ${restartInsteadOfReboot ? 'restart' : 'reboot'}"
    executeReboot()
}

def uninstalled() {
    log.info "Uninstalling Rebooter-MAX"
    unschedule()
    unsubscribe()
}

def prefMain() {
    return dynamicPage(name: "prefMain", title: "Rebooter-MAX Configuration", install: true, uninstall: true) {
        section("") {
            input "rebootSecurity", "bool", title: "Is Hub Security enabled?", defaultValue: false
            if (rebootSecurity) {
                input "rebootUsername", "string", title: "Hub Security username", required: true
                input "rebootPassword", "password", title: "Hub Security password", required: true
            }
            input "rebootType", "enum", title: "Trigger type", options: ["Time", "Button"], defaultValue: "Time", submitOnChange: true

            if (rebootType == "Time") {
                input "rebootTime", "time", title: "Reboot time", required: true
                input "rebootDays", "enum", title: "Reboot days", required: true, multiple: true,
                options: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
                input "rebootFrequency", "enum", title: "Frequency", options: ["Weekly", "Bi-Weekly", "Monthly"], defaultValue: "Weekly"
                input "rebootDayOfMonth", "number", title: "Day of the month (for monthly reboot)", required: false
            } else if (rebootType == "Button") {
                input "rebootButton", "capability.pushableButton", title: "Trigger button", required: true
            }
            input "restartInsteadOfReboot", "bool", title: "Restart instead of full reboot", defaultValue: false
        }
    }
}

def executeReboot() {
    try {
        if (restartInsteadOfReboot) {
            log.info "======================================"
            log.info "=== Rebooter-MAX - Initiating Restart"
            log.info "======================================"
            performRestart()
        } else {
            log.info "=========================================="
            log.info "=== Rebooter-MAX - Initiating Full Reboot"
            log.info "=========================================="
            performReboot()
        }
    } catch (Exception e) {
        log.error "Failed to execute reboot: ${e}"
    }
}

def performReboot() {
    def headers = [:]
    if (rebootSecurity) {
        headers.Authorization = "Basic " + "${rebootUsername}:${rebootPassword}".bytes.encodeBase64()
    } else {
        log.warn "Rebooter-MAX - Security disabled - proceeding without authentication"
    }
    httpPost(
        uri: "http://${location.hub.localIP}:8080",
        path: "/hub/reboot",
        headers: headers
    ) {
        log.debug "Rebooter-MAX - Reboot command sent"
    }
}

def performRestart() {
    def headers = [:]
    if (rebootSecurity) {
        headers.Authorization = "Basic " + "${rebootUsername}:${rebootPassword}".bytes.encodeBase64()
    } else {
        log.warn "Rebooter-MAX - Security disabled - Proceeding without authentication"
    }
    httpPost(
        uri: "http://${location.hub.localIP}:8080",
        path: "/hub/restart",
        headers: headers
    ) {
        log.debug "Rebooter-MAX - Restart command sent"
    }
}

def logConfiguration() {
    log.info """
    ===============================
    === Rebooter-MAX Configuration ====
    - Type:        ${rebootType}
    ${rebootType == "Time" ? "- Schedule:    ${rebootFrequency} on ${rebootDays.join(', ')} at ${new Date(timeToday(rebootTime).time).format("HH:mm z", location.timeZone)}\n|" : ""}\
    - Security:    ${rebootSecurity ? "Enabled (user: ${rebootUsername})" : "Disabled"}
    - Action:      ${restartInsteadOfReboot ? "Restart Process" : "Full Reboot"}
    ==============================""".stripMargin()
}

def logSchedule(time) {
    def nextExecutions = []
    def nextRunTime

    // Calculate next execution manually based on frequency
    if (rebootFrequency == "Weekly") {
        nextRunTime = getNextWeeklyRun(time)
    } else if (rebootFrequency == "Bi-Weekly") {
        nextRunTime = getNextBiWeeklyRun(time)
    } else if (rebootFrequency == "Monthly") {
        nextRunTime = getNextMonthlyRun(time)
    }

    if (nextRunTime) {
        nextExecutions << new Date(nextRunTime).format("EEE, MMM dd 'at' HH:mm z", location.timeZone)
    }

    log.info """
    ===============================
    === Rebooter-MAX Schedule =======
    - Scheduled ${rebootFrequency} reboots:
    - Days:       ${rebootDays.join(', ')}
    - Time:       ${time.format("HH:mm z", location.timeZone)}
    - Next: ${nextExecutions.first()}
    ===============================
    """
}

def getNextWeeklyRun(time) {
    def now = new Date()
    def todayShort = now.format('EEE', location.timeZone).toUpperCase()
    
    // Convert rebootDays to uppercase short forms (e.g., ["TUE", "FRI"])
    def rebootDaysShort = rebootDays.collect { it.substring(0, 3).toUpperCase() }
    
    // Sort days to maintain order in the week
    def weekDays = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"]
    
    // Find the next valid reboot day
    def todayIndex = weekDays.indexOf(todayShort)
    def nextDayIndex = rebootDaysShort.collect { weekDays.indexOf(it) }
                                   .find { it >= todayIndex }
                                   ?: weekDays.indexOf(rebootDaysShort[0]) // Wrap to next week if needed
    
    // Determine the days until next reboot
    def daysUntilNext = nextDayIndex - todayIndex
    if (daysUntilNext < 0 || (daysUntilNext == 0 && now.after(time))) {
        daysUntilNext += 7 // Move to next week's occurrence
    }
    
    // Compute next run date
    def nextRun = new Date(now.time + (daysUntilNext * 24 * 60 * 60 * 1000))
    nextRun.setHours(time.hours)
    nextRun.setMinutes(time.minutes)
    nextRun.setSeconds(0)

    return nextRun.time
}

def getNextBiWeeklyRun(time) {
    def now = new Date()
    def nextRun = timeToday(rebootTime, location.timeZone)

    // Schedule bi-weekly by adding 14 days
    def lastRebootDate = state.lastReboot ? new Date(state.lastReboot) : now
    nextRun = new Date(lastRebootDate.time + (14 * 24 * 60 * 60 * 1000))  // Add 14 days
    return nextRun.time
}

def getNextMonthlyRun(time) {
    def now = new Date()
    def nextRun = timeToday(rebootTime, location.timeZone)

    // Schedule for the first day of next month
    nextRun.setDate(1) // Set the first day of the month
    return nextRun.time
}

image

1 Like

As of a few releases back utilizing the loopback address to reboot does not require logging into the hub even if security is in use.

2 Likes

Can you update this script to reboot daily?

It can already do that. Select all of the days under "reboot days" and frequency of "weekly".

2 Likes

I would not recommend rebooting everyday .. 1 time a week maybe ?
Why do you need to reboot everyday ?

1 Like

I reboot every day. Have for years. Same with my perimeter firewall.

Do I "need to"? Probably not.

Does it hurt anything? Apparently not.

The original C7 with the crappy radio firmware rebooting would make a temporary mess of things. So back then we were pushing people not to reboot daily. Doesn't seem to be that problematic anymore so probably not going to hurt anything. However, if you find that you NEED to reboot daily, that should be a sign of something else going wrong. Hub should be able to run for weeks without rebooting.

3 Likes