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