Monitoring an APC Back-UPS With openHAB 2

openHAB doesn’t have a binding for APC UPS devices. Luckily, there are other ways to integrate them into your openHAB setup.

In the following, I’m describing how the exec binding can be used to regularly poll the UPS status using the apcaccess command-line utility.

The result will look somehow like this:

I’m using a APC Back-UPS BX 700VA here—a relatively cheap model with Schuko outlets and a USB connector.

Prerequisites

  • APC Back-UPS connected and configured (the apcaccess command needs to work)
  • Working openHAB 2 installation

Configuration

We’ll need to add a binding, a thing, various items, and finally an entry in the sitemap.

Bindings

As aforementioned, I’m using the exec binding to poll the status of the apcaccess command-line utility on a regular basis. To install the exec binding, add exec to the binding list in services/addons.cfg.

Example:

# A comma-separated list of bindings to install (e.g. "sonos,knx,zwave")
binding = exec

Things

To make the UPS status available to openHAB, create a new file called apc.things in your things directory and insert the following line:

Thing exec:command:apc [command="/sbin/apcaccess -u", interval=5, timeout=2, autorun=false]

The -u parameter will suppress any units (volts, percent, seconds, etc.) This makes parsing of the output much easier.

Note: The command above assumes that the apcaccess command-line utility is located in /sbin/. Adjust this path if necessary.

Items

Create a new file called apc.items in your items directory and insert the following contents:

String APC_Raw { channel="exec:command:apc:output" }

Group gAPC

String      APC_APC         (gAPC)
DateTime    APC_DATE        (gAPC)
String      APC_HOSTNAME    (gAPC)
String      APC_VERSION     (gAPC)
String      APC_UPSNAME     (gAPC)
String      APC_CABLE       (gAPC)
String      APC_DRIVER      (gAPC)
String      APC_UPSMODE     (gAPC)
DateTime    APC_STARTTIME   (gAPC)
String      APC_MODEL       (gAPC)
String      APC_STATUS      (gAPC)
Number      APC_LINEV       (gAPC)
Number      APC_LOADPCT     (gAPC)
Number      APC_BCHARGE     (gAPC)
Number      APC_TIMELEFT    (gAPC)
Number      APC_MBATTCHG    (gAPC)
Number      APC_MINTIMEL    (gAPC)
Number      APC_MAXTIME     (gAPC)
String      APC_SENSE       (gAPC)
Number      APC_LOTRANS     (gAPC)
Number      APC_HITRANS     (gAPC)
Number      APC_ALARMDEL    (gAPC)
Number      APC_BATTV       (gAPC)
String      APC_LASTXFER    (gAPC)
Number      APC_NUMXFERS    (gAPC)
DateTime    APC_XONBATT     (gAPC)
Number      APC_TONBATT     (gAPC)
Number      APC_CUMONBATT   (gAPC)
DateTime    APC_XOFFBATT    (gAPC)
String      APC_SELFTEST    (gAPC)
String      APC_STATFLAG    (gAPC)
String      APC_SERIALNO    (gAPC)
DateTime    APC_BATTDATE    (gAPC)
Number      APC_NOMINV      (gAPC)
Number      APC_NOMBATTV    (gAPC)
Number      APC_NOMPOWER    (gAPC)
String      APC_FIRMWARE    (gAPC)

The first item called APC_Raw holds the full output of the apcaccess utility. The other items don’t do anything yet—in the next step, we will create a rule to parse the output and assign values the individual items. The group gAPC is required by the rule, so don’t forget to specify it here.

Note: If you have a different model than I have, this list might not be 100% correct. Run apcaccess to get a list of parameters available for your UPS, and adjust the list accordingly.

Rules

Create a new file called apc.rules in your rules directory and insert the following contents:

import java.io.BufferedReader
import java.io.StringReader
import java.util.Calendar
import java.text.SimpleDateFormat
import java.text.ParseException

rule "APC: Parse raw output from command-line tool"
when
	Item APC_Raw changed
then
	var output = APC_Raw.state.toString
	var String line
	var String[] buffer
	var String value
	var bufReader = new BufferedReader(new StringReader(output))
	var Calendar cal = Calendar.getInstance()
	var formatStrings = newArrayList('yyyy-MM-dd HH:mm:ss Z', 'yyyy-MM-dd')
		
	while((line = bufReader.readLine()) != null) {
		buffer = line.split(':', 2)
		for (var i = 0; i < 2; i++) {
			buffer.set(i, buffer.get(i).trim())
    	}
    	
    	// check if there’s an item for this key (e.g. APC_STARTTIME)
    	var item = gAPC.members.findFirst[name.equals("APC_" + buffer.get(0))]

		if (item != null) {
			value = buffer.get(1)
			
			// DateTime item: try to parse date
			if (item.type == 'DateTime') {
				var succeeded = false
				for (String formatString : formatStrings) {
					try {
						if (!succeeded) { // no `break` statement in openHAB :(
							cal.setTime(new SimpleDateFormat(formatString).parse(value))
							item.postUpdate(new DateTimeType(cal).toString)
							succeeded = true
						}
					} catch (ParseException e) { }
				}
			// String or Number: just update the value
			} else {
				item.postUpdate(value)	
			}
		}
	}
	
end

Whenever APC_Raw changes, this rule will process the command’s output and will fill in the individual items with the correctly parsed values (these can be of type String, Number, or DateTime).

Sitemap

Finally, to expose these values, create an additional entry in your sitemap. If you’re using the default sitemap, open the file sitemaps/default.sitemap and add the following:

sitemap default label="Home" {

	// …

	Frame label="System Status" {
		Text label="UPS [%s]" item=APC_STATUS valuecolor=[APC_STATUS=="ONBATT"="#FF0000",APC_STATUS=="ONLINE"="#008000"] {
			Text item=APC_MODEL label="Model [%s]"
			Text item=APC_STATUS label="Status [%s]" valuecolor=[APC_STATUS=="ONBATT"="#FF0000",APC_STATUS=="ONLINE"="#008000"]
			Text item=APC_STARTTIME label="Start Time [%1$td/%1$tm/%1$ty %1$tH:%1$tM]"
			Text item=APC_LOADPCT label="Load [%.1f %%]"
			Text item=APC_TIMELEFT label="Time Left [%.1f min]"
			Text item=APC_LINEV label="Line Voltage [%.1f V]"
			Text item=APC_BATTV label="Battery Voltage [%.1f V]"
			Text item=APC_BATTDATE label="Battery Date [%1$td/%1$tm/%1$ty]"
		}
	}
}

As you can see, I haven’t added all available values here. Adjust this to your needs.

That’s it!

Bonus: Push Notification On Power Outage

To get a notification on your mobile device whenever the power fails (and also when it returns), configure the pushover binding and add the following rule to your apc.rules:

rule "APC: Notify power outage/return"
when
	Item APC_STATUS changed
then

	var msg = ''

	if (APC_STATUS.state == "ONLINE") {
		msg = "Power has returned"
	} else if (APC_STATUS.state == "ONBATT") {
		msg = "Power outage detected, remaining battery life: " + APC_TIMELEFT.state + " min"
	}
	
	if (msg != '') {
		logInfo("APC", msg)
		pushover(msg)
	}
end

Further Reading