Can RM do simple math?

I have a UPS that I get an attribute that is presented in seconds. Mostly over 5000 seconds. I want to change that to hours. So the math is simple 5000/60. But how and where is this done? Any exmples greatly appreciated

Assign your attribute to a number variable. Then do a set variable and do variable math. Then you can divide the number by 60

1 Like

Or divide by 3600 if you want hours. :wink:

5 Likes

We’re assuming that you know how to or can find how to:

  1. Create a variable in RM
  2. Store a device attribute in the variable
  3. User variable math to do the calculation

If you are stuck with any of those steps, let us know and post a screenshot of where your rule at the point where you’re stuck so that someone can help.

4 Likes

Well this is embarassing, I am stuck on #1 !

There is no way to add a link to an attribute for a variable (That I can find). The attribute will change and that is what I want to divide by 60 to get the number of minutes the UPS will run everything. I want to put that in a tile on a dashboard. Sorry if I am overlooking the obvious

What Driver are you using for the UPS? It would be much quicker to simply modify the driver to divide the seconds by 3600, giving you hours without needing the complexity of rule machine.

I am using the NUT driver. I thought about that and looked at the Child device code but couldn't figure out how to make that change

1 Like

Below shows putting a device attribute into a variable. Does that help?

Here you go! Give this version a try. I simply added a new custom Attribute that should display the runtime in hours. Simply copy and paste this version over top of your existing one, and upon the next update, you should see the new attribute appear.

Removed version with bug. 

See this post for working version

2 Likes

The new attribute doesn't show up :frowning:

The result of that runtimeinhours is .87 Is that why it isn't showing up?

I do not have the attribute in the "Select variable to set" I tried to set it up in the Global Variables but there wasn't a way to doit.

Do you see anything in the Events history for the new attribute?

Any errors in the logs?

got this in the log

2022-01-17 03:59:47.697 pm errorgroovy.lang.MissingMethodException: No signature of method: java.lang.Float.Round() is applicable for argument types: (java.lang.Integer) values: [1]
Possible solutions: round(int), round(), trunc(int), trunc(), find(), mod(java.lang.Number) on line 128 (method parseVAR)

line 128 is the hours.hours.round(1)

Instead of changing it to hours why not change it to Minutes?

I updated the code in my earlier post. Give that one a try.

You asked for hours, and thus that is what I attempted to provide. It should provide 1 decimal place of precision, so you should get something to appear. Hopefully I have fixed the error. Let me know.

Same error

Try this version. I found the bug. Simple typo.

/**
 *  NUT Child UPS Device Type for Hubitat
 *  Peter Gulyas (@guyeeba)
 *
 *  Usage:
 *  See NUT UPS Driver
 * 
 *  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:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  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.
 *
 */

metadata {
    definition (name: "NUT Child UPS", namespace: "guyee", author: "Peter Gulyas") {
        capability "Refresh"
		capability "TemperatureMeasurement"
		capability "PowerSource"
		
		// Outlet capabilities
		capability "VoltageMeasurement"		
		capability "PowerMeter"
		capability "Battery"
	}
	
	attribute "batteryRuntimeSecs", "Number"
	attribute "batteryRuntimeHrs", "Number"
    attribute "batteryType", "String"
	attribute "batteryVoltage", "Number"

	attribute "deviceManufacturer", "String"
	attribute "deviceModel", "String"
	attribute "deviceType", "String"
	attribute "deviceFirmware", "String"
	attribute "deviceNominalPower", "Number"
	
	attribute "driverName", "String"
	attribute "driverVersion", "String"
	attribute "driverVersionInternal", "String"
	attribute "driverVersionData", "String"
	
	attribute "load", "Number"
	attribute "status", "String"

	attribute "outputVoltage", "Number"
	attribute "outputVoltageNominal", "Number"
	attribute "outputFrequency", "Number"
	attribute "outputFrequencyNominal", "Number"

	attribute "outletDescription", "String"
	attribute "outletSwitchable", "Boolean"
	preferences { 
	input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true
	}
}

def refresh() {
    parent.childRefresh(getUPSName())
}

// Adds a '+' prefix to the first item's value in case it has a child item. Makes processing much more easier in switch blocks
def getLeafDesignation(String[] msg) {
	return (msg.length == 1 ? "" : "+") + msg[0]
}

def parseVAR(String[] msg) {
	def key = msg[0].split('\\.', -1)
	def value = msg.length > 1 ? msg[1] : null
	
	switch (getLeafDesignation(key)) {
		case "+battery":
			parseBATTERY(key.drop(1), value)
			break
		case "+device":
			parseDEVICE(key.drop(1), value)
			break
		case "+driver":
			parseDRIVER(key.drop(1), value)
			break
		case "+ups":
			parseUPS(key.drop(1), value)
			break
		case "+input":
			parseINPUT(key.drop(1), value)
			break
		case "+output":
			parseOUTPUT(key.drop(1), value)
			break
		case "+outlet":
			parseOUTLET(key.drop(1), value)
			break
		default:
			displayDebugLog("ParseVAR: Couldn't process message: \"${msg}\"")
	}
}

def parseBATTERY(String[] msg, String value) {
	switch (getLeafDesignation(msg)) {
		case "charge":
			sendEvent( [
				name: 'battery',
				value: Float.parseFloat(value),
				unit: "%",
				descriptionText: "Battery is at ${Float.parseFloat(value)}%"
			])
			break;
		case "voltage":
			sendEvent( [
				name: 'batteryVoltage',
				value: Float.parseFloat(value),
				unit: "V",
				descriptionText: "Battery voltage is ${Float.parseFloat(value)}V"
			])
			break;
		case "runtime":
			sendEvent( [
				name: 'batteryRuntimeSecs',
				value: Integer.parseInt(value),
				unit: "sec",
				descriptionText: "Remaining runtime is ${Integer.parseInt(value)} seconds"
			])
            float hours =  Float.parseFloat(value)/3600
            hours = hours.round(1)
			sendEvent( [
				name: 'batteryRuntimeHrs',
				value: hours,
				unit: "hours",
				descriptionText: "Remaining runtime is ${hours} hours"
			])
			break;
		case "type":
			sendEvent( [
				name: 'batteryType',
				value: value,
				descriptionText: "Battery type is ${value}"
			])
			break;
		default:
			displayDebugLog("ParseBATTERY: Couldn't process message: \"${msg}\"")
	}
}

def parseDEVICE(String[] msg, String value) {
	switch (getLeafDesignation(msg)) {
		case "mfr":
			sendEvent( [
				name: 'deviceManufacturer',
				value: value,
				descriptionText: "Device manufacturer is ${value}"
			])
			break;
		case "type":
			sendEvent( [
				name: 'deviceType',
				value: value,
				descriptionText: "Device type is ${value}"
			])
			break;
		case "model":
			sendEvent( [
				name: 'deviceModel',
				value: value,
				descriptionText: "Device model is ${value}"
			])
			break;
		default:
			displayDebugLog("ParseDEVICE: Couldn't process message: \"${msg}\"")
	}
}

def parseDRIVER(String[] msg, String value) {
	switch (getLeafDesignation(msg)) {
		case "name":
			sendEvent( [
				name: 'driverName',
				value: value,
				descriptionText: "Driver name is ${value}"
			])
			break;
		case "version":
			sendEvent( [
				name: 'driverVersion',
				value: value,
				descriptionText: "Driver version is ${value}"
			])
			break;
 		case "+version":
			parseDRIVER_VERSION(msg.drop(1), value)
			break;
		case "+parameter": // Not really interesting
			break;
		default:
			displayDebugLog("ParseDRIVER: Couldn't process message: \"${msg}\"")
	}
}

def parseDRIVER_VERSION(String[] msg, String value) {
	switch (getLeafDesignation(msg)) {
		case "internal":
			sendEvent( [
				name: 'driverVersionInternal',
				value: value,
				descriptionText: "Driver internal version is ${value}"
			])
			break;
		case "data":
			sendEvent( [
				name: 'driverVersionData',
				value: value,
				descriptionText: "Driver version data is ${value}"
			])
			break;
		default:
			displayDebugLog("ParseDEVICE: Couldn't process message: \"${msg}\"")
	}
}

def parseUPS(String[] msg, String value) {
	switch (getLeafDesignation(msg)) {
		case "temperature":
			sendEvent( [
				name: 'temperature',
				value: Float.parseFloat(value),
				unit: "�C",
				descriptionText: "Temperature is ${Float.parseFloat(value)}�C"
			])
			break;
		case "load":
			sendEvent( [
				name: 'load',
				value: Float.parseFloat(value),
				unit: "%",
				descriptionText: "UPS load is ${Float.parseFloat(value)}%"
			])
			break;
		case "firmware":
			sendEvent( [
				name: 'deviceFirmware',
				value: value,
				descriptionText: "Device firmware version is ${value}"
			])
			break;
		case "mfr":
			sendEvent( [
				name: 'deviceManufacturer',
				value: value,
				descriptionText: "Device manufacturer is ${value}"
			])
			break;
		case "model":
			sendEvent( [
				name: 'deviceModel',
				value: value,
				descriptionText: "Device model is ${value}"
			])
			break;
		case "status":
			def statusCodeMap = [
				'OL': 'Online',
				'OB': 'On Battery',
				'LB': 'Low Battery',
				'HB': 'High Battery',
				'RB': 'Battery Needs Replaced',
				'CHRG': 'Battery Charging',
				'DISCHRG': 'Battery Discharging',
				'BYPASS': 'Bypass Active',
				'CAL': 'Runtime Calibration',
				'OFF': 'Offline',
				'OVER': 'Overloaded',
				'TRIM': 'Trimming Voltage',
				'BOOST': 'Boosting Voltage',
				'FSD': 'Forced Shutdown'
			]
			def statuses = value.split(" ")
			String statusText = statuses?.collect { statusCodeMap[it] }.join(", ")

			sendEvent( [
				name: 'status',
				value: statusText,
				descriptionText: "Device status is ${statusText}"
			])

			def powersource = "unknown"
			if (statuses.contains('OL'))
				powersource = "mains"
			else if (statuses.contains('OB'))
				powersource = "battery"

			sendEvent( [
				name: 'powerSource',
				value: powersource,
				descriptionText: "Power source is ${powersource}"
			])

			break;
		case "vendorid": // Not really interesting
			break;
		case "timer": // Not really interesting
			break;
		case "start": // Not really interesting
			break;
		case "productid": // Not really interesting
			break;
		case "delay": // Not really interesting
			break;
		case "beeper": // Not really interesting
			break;
		case "+power":
			parseUPS_POWER(msg.drop(1), value)
			break;
		default:
			displayDebugLog("ParseUPS: Couldn't process message: \"${msg} - ${value}\"")
	}
}

def parseUPS_POWER(String[] msg, String value) {
	switch (getLeafDesignation(msg)) {
		case "nominal":
			sendEvent( [
				name: 'deviceNominalPower',
				value: Integer.parseInt(value),
				unit: "VA",
				descriptionText: "Device nominal power is ${Integer.parseInt(value)}VA"
			])
		break;
		default:
			displayDebugLog("ParseUPS_POWER: Couldn't process message: \"${msg} - ${value}\"")
	}
}
			
def parseINPUT(String[] msg, String value) {
	switch (getLeafDesignation(msg)) {
		case "voltage":
			sendEvent( [
				name: 'voltage',
				value: Float.parseFloat(value),
				unit: "V",
				descriptionText: "Input voltage is ${Float.parseFloat(value)}V"
			])
			break;
		default:
			displayDebugLog("ParseINPUT: Couldn't process message: \"${msg}\"")
	}
}

def parseOUTPUT(String[] msg, String value) {
	switch (getLeafDesignation(msg)) {
		case "voltage":
			sendEvent( [
				name: 'outputVoltage',
				value: Float.parseFloat(value),
				unit: "V",
				descriptionText: "Output voltage is ${Float.parseFloat(value)}V"
			])
			break;
		case "+voltage":
			parseOUTPUT_VOLTAGE(msg.drop(1), value)
			break;
		case "frequency":
			sendEvent( [
				name: 'outputFrequency',
				value: Float.parseFloat(value),
				unit: "Hz",
				descriptionText: "Output frequency is ${Float.parseFloat(value)}Hz"
			])
			break;
		case "+frequency":
			parseOUTPUT_FREQUENCY(msg.drop(1), value)
			break;
		default:
			displayDebugLog("ParseOUTPUT: Couldn't process message: \"${msg}\"")
	}
}

def parseOUTPUT_VOLTAGE(String[] msg, String value) {
	switch (getLeafDesignation(msg)) {
		case "nominal":
			sendEvent( [
				name: 'outputVoltageNominal',
				value: Float.parseFloat(value),
				unit: "V",
				descriptionText: "Nominal output voltage is ${Float.parseFloat(value)}V"
			])
			break;
		default:
			displayDebugLog("ParseOUTPUT_VOLTAGE: Couldn't process message: \"${msg}\"")
	}
}

def parseOUTPUT_FREQUENCY(String[] msg, String value) {
	switch (getLeafDesignation(msg)) {
		case "nominal":
			sendEvent( [
				name: 'outputFrequencyNominal',
				value: Float.parseFloat(value),
				unit: "Hz",
				descriptionText: "Nominal output frequency is ${Float.parseFloat(value)}Hz"
			])
			break;
		default:
			displayDebugLog("ParseOUTPUT_FREQUECY: Couldn't process message: \"${msg}\"")
	}
}

def parseOUTLET(String[] msg, String value) {
	switch (msg[0]) {
		case "id":
		break;
		case "desc":
			sendEvent( [
				name: 'outletDescription',
				value: value,
				descriptionText: "Outlet description is ${value}"
			])
			break;
		case "switchable":
			Boolean isSwitchable = (value == "yes")
			sendEvent( [
				name: 'outletSwitchable',
				value: isSwitchable,
				descriptionText: "Outlet is ${isSwitchable ? "" : "not"} switchable"
			])
			break;			
		default:
			if (msg[0].isNumber()) {
				def outletId = msg[0].toInteger()
				def childOutlet = getChildDevice(getOutletDNID(outletId))
				
				if (childOutlet == null) {
					displayDebugLog "Outlet ${outletId} not found, creating"
					childOutlet = addChildDevice("guyee", "NUT Child UPS Outlet", getOutletDNID(outletId), [name: "Outlet ${outletId}", label: "Outlet ${outletId}", completedSetup: true, isComponent: false ])
				}
				
				childOutlet.parseOUTLET(msg.drop(1), value)
			} else {
				displayDebugLog("ParseOUTLET: Couldn't process message: \"${msg}\"")
			}
	}
}
private displayDebugLog(message) {
	if (logEnable) log.debug "${device.displayName}: ${message}"
}

def getUPSName() {
	device.deviceNetworkId.substring(device.deviceNetworkId.lastIndexOf("-") + 1)
}

def getOutletDNID(Integer id) {
	return "${device.deviceNetworkId}-outlet${id}"
}
4 Likes

That got it! Thanks alot

2 Likes