[Release] Windows PC Battery Driver

I have my windows tablet PC setup as a kiosk with strict battery management rules, but there isn't a way to control the battery charge/discharge functions natively. So I have prepared the below driver configuration for Hubitat to read the PC's battery state, I then use RM to automatically charge it.

You have to set up a Task to run in Windows which posts a device battery report to a network shared location. The driver allows you to add your PCs as new devices, each device instance reads the designated powershell report output within Hubitat. Using the device's battery state, I allow it to run a 45%-75% charge cycle using rule machine and a smart plug, which enables my 2.5 hour battery to become a very responsive kiosk with a ~24 hour charge cycle banded within this 30% charge range with occasional use.

Windows 7-8-10-11 PC Setup:

Power Plan - Advanced Settings
Sleep - Allow Wake Timers

Settings - Sign In Options
If you've been away, Require Sign In - Never

Task Scheduler - Create Task
1. New Trigger - Repeat Task Every: (your pick) - Indefinitely
2. First New Action (Overwrite report with current date/time) - Start Program - "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
Add Arguments: Get-Date | Out-File -FilePath .\batteryreport.txt
Start In (shared network location to save the report)
3. Second New Action (Add battery percent to new report) - Start Program - "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
Add Arguments: WMIC PATH Win32_Battery Get EstimatedChargeRemaining | Out-File -FilePath .\batteryreport.txt -Append
Start In (shared network location to save the report)
4. Conditions - uncheck start only on AC && Allow Wake Computer
5. Settings - Allow Run task as soon as it is missed && Uncheck Stop after 3 days

I don't know your particular network file sharing setup, but the link to the report, when entered to a web browser on a different device on your network, should prompt download of the text file without username/password. Mine is posted to a NAS with sharing for that specific file (whose address is always static and the file overwritten by the scheduled windows task)

Hubitat Laptop Battery Driver:


/*
 * Laptop Battery
 *
 *
 *  Change History:
 *
 *      Date          Source        Version     What                                              URL
 *      ----          ------        -------     ----                                              ---
 *      2022-01-22    pentalingual  0.1.0       Starting version
 *
 */

import java.text.SimpleDateFormat

static String version() { return '0.1.0' }

metadata {
    definition(
            name: "Laptop Battery",
            namespace: "pentalingual",
            author: "Andrew Nunes",
    )  {
        capability "Actuator"
        capability "Refresh"
        capability "Battery"

        attribute "battery", "number"
        attribute "lastCheckin", "Date"
        attribute "lastRefresh", "Date"
    }
}

preferences {
  input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: false
  input name: "txtEnable", type: "bool", title: "Enable descriptionText logging", defaultValue: true
  input name: "decodeEnable", type: "bool", title: "Enable decoding/Clean non-ANSI characters", defaultValue: true
  input name: "fileLocation", type: "string", title: "Link to the BatteryReport", defaultValue: null
  input("autoUpdatehour", "number", title: "Enable automatic update every n hours (enter 0 for never)", defaultValue: 0, required: true, displayDuringSetup: true)
  input("autoUpdatemin", "number", title: "Enable automatic update every n minutes (enter 0 for never)", defaultValue: 0, required: true, displayDuringSetup: true)
}


def logsOff() {
    log.warn "debug logging disabled..."
    device.updateSetting("logEnable", [value: "false", type: "bool"])
}

def installed() {
    logging("installed()", 100)
    unschedule()
    refresh()
}

def updated() {
    log.info "updated..."
    log.warn "debug logging is: ${logEnable}"
    log.warn "description logging is: ${txtEnable}"
    if (logEnable) {
        if (autoUpdatemin+autoUpdatehour ==0)log.warn("Update: Automatic Update DISABLED")
    }
    if (logEnable) {
        if (autoUpdatemin+autoUpdatehour>0)log.debug("Update: Automatic Update enabled")
    }
    if (logEnable) runIn(1800, logsOff)
    if (logEnable) log.debug("autoupdate: Next scheduled refresh set")
    unschedule()
    refresh()
    schedUpdate()

}

def refresh() {
       runCmd()
}

def schedUpdate() {
    unschedule()
    if (txtEnable) log.info("schedUpdate: Refresh schedule cleared ...")
    if (autoUpdatemin+autoUpdatehour>0) {
        if (txtEnable) log.debug("Update: Setting next scheduled refresh...")
        if (autoUpdatemin+autoUpdatehour>0) { 
            if (autoUpdatemin>0) { 
                if (autoUpdatehour>0) { 
            schedule("0 0/${autoUpdatemin} * * * ?", refresh)
                } else {
                    schedule("0 0/${autoUpdatemin} 0/${autoUpdatehour} * * ?", refresh)
                } 
            } else {
               schedule("0 0 0/${autoUpdatehour} * * ?", refresh)
            }
        }
        if (autoUpdatemin+autoUpdatehour>0) log.info("Update: Next scheduled refresh set")
    }
}

def runCmd() {
    now = new Date()

    sendEvent(name: "lastRefresh", value: now)
    
   checkBattery()
}

def checkBattery() {
    if (device.currentValue("lastRefresh") == (null)) {
        runCmd()
    } else {
        batteryPreviouslySet = true
        existingBatteryreport = "${battery}"
    }
    readFile()
}

def readFile(){
    if (logEnable) log.debug("Downloading the newest report at ${fileLocation}")
    uri = fileLocation
    def queryResults = ""
    def params = [
        uri: uri,
        contentType: "text/html; charset=ANSI",
                headers: [
				"Cookie": cookie
            ]
    ]

    try {
        httpGet(params) { resp ->
            if(resp!= null) {
                printLine = resp.getData() as String
                printLines = printLine.readLines()
                String BL = printLines[12]
                String LCI = printLines[2]
                if(decodeEnable) {
                String Remover = BL.substring(0,1)
                String BLC = BL.replaceAll(Remover, '')
                int BLI = BLC.toInteger()
                String LCIFinal = LCI.replaceAll(Remover, '')
                Date LCIDate = Date.parse('EEEEE, MMMMM d, yyyy h:mm:ss aa', LCIFinal)
                sendEvent(name: "lastCheckin", value:  LCIDate.format('EEE MMM d HH:mm:ss z yyyy'), descriptionText: descriptionText)
                sendEvent(name: "battery", value: BLI)
                if (txtEnable) log.info("Battery reported as ${BLI}% on ${LCIDate}")
                } else {
                    int BLI = BL.toInteger()
                 Date LCIDate = Date.parse('EEEEE, MMMMM d, yyyy h:mm:ss aa', LCI)
                sendEvent(name: "lastCheckin", value:  LCIDate.format('EEE MMM d HH:mm:ss z yyyy'), descriptionText: descriptionText)
                sendEvent(name: "battery", value: BLI)
                }
            }
            else {
                log.error "Null Response"
            }
        }
    } catch (exception) {
        log.error "Read Error: ${exception.message}"
        return null;
    }
}

2 Likes

Could you clarify the Out-File -FilePath. Or maybe an example.

Hey j715 - sorry I missed this notification. Out-File is a powershell command to dump the powershell result into a file. The file path can be whatever you specify (powershell will write in the folder it is currently at), so in my case I have the task start powershell in my network folder within which I wish the file to be created, and then it writes a file there called batteryreport.txt

There are two out file commands, the first creates a new (or overwrites the existing) file with the current system datetime for our timestamp; and the second appends the device charge state. Upon openning in word or notepad, the resultant text file looks simply like this:

Friday, February 10, 2023 10:02:03 AM

EstimatedChargeRemaining

86

Then I use this groovy driver to read the network file on a schedule and update the hubitat device attributes with our powershell report.