APC UPS Binding

, ,

Hello all,

I’m writing this here as I think it is the right sub forum. And might be interesting for others as well - as soon as the problems are solved.

My goal is to monitor two APC UPS devices. I took some information, configuration and scripts from here.

Looks pretty decent but I am stuck with one error message:

2016-06-21 13:24:06.804 [ERROR] [o.o.c.s.ScriptExecutionThread ] - Error during the execution of rule 'USV Handling': The name 'compile(<XFeatureCallImplCustom>)' cannot be resolved to an item or type.

Here’s the content of my configuration files:

Items file:

String USV_Log    "kompletter Inhalt des Logs [%s]"       { exec="<[/home/pi/USV_dump_log 3551:60000:REGEX((.*))]" }
String USV_Status "USV Status [%s]" <apc>   (USV)
Number USV_LineV  "USV Input Volts [%.1f V]"        <apc>   (USV)
Number USV_OutputV        "USV Output Volts [%.1f V]"       <apc>   (USV)
Number USV_Load   "USV Load [%.1f %%]"      <apc>   (USV)
Number USV_BattCharge     "USV Battery Charge [%.1f %%]"    <apc>   (USV)
Number USV_MBattChg       "USV Battery Change [%.1f %%]"    <apc>   (USV)
Number USV_TimeLeft       "USV Time Left [%.1f min]"        <apc>   (USV)
Number USV_ITemp  "USV Temperatur [%.1f °C]"        <selfTemperature>       (USV)
Number USV_BattVoltage    "USV Battery Volts [%.1f V]"      <apc>   (USV)
String USV_BattDate       "USV Battery Date [%s]"   <apc>   (USV)

usv.rules:

val org.eclipse.xtext.xbase.lib.Functions$Function4 UPS_GetNumValue = [
    org.openhab.core.library.items.StringItem logITem,
    org.openhab.core.library.items.NumberItem upsITem,
    String sExpr,
    String logline |
        var String ups_log
        ups_log = logITem.state.toString
        var Pattern ups_pattern
        ups_pattern = Pattern::compile(sExpr)
        var Matcher ups_matcher
        ups_matcher = ups_pattern.matcher(ups_log)
        ups_matcher.find()
        var String ups_result
        ups_result = ups_matcher.group(1)
        upsITem.postUpdate(ups_result)
]

val org.eclipse.xtext.xbase.lib.Functions$Function4 UPS_GetStrValue = [
    org.openhab.core.library.items.StringItem logITem,
    org.openhab.core.library.items.StringItem upsITem,
    String sExpr,
    String logline |
        var String ups_log
        ups_log = logITem.state.toString
        var Pattern ups_pattern
        ups_pattern = Pattern::compile(sExpr)
        var Matcher ups_matcher
        ups_matcher = ups_pattern.matcher(ups_log)
        ups_matcher.find()
        var String ups_result
        ups_result = ups_matcher.group(1)
        upsITem.postUpdate(ups_result)
]

rule "USV Handling"
when
    Item USV_Log received update
then
    UPS_GetStrValue.apply(USV_Log,USV_Status,"(?<=STATUS : )(.*?)(?= )","LogLine")
    logInfo("USV.Handler","USV Status: "+USV_Status.state.toString+"")

    UPS_GetNumValue.apply(USV_Log,USV_LineV,"LINEV \\: ([-+]?[0-9]*\\.?[0-9]) Volts","LogLine")
    logInfo("USV.Handler","USV Input Volts: "+USV_LineV.state.toString+" Volt")

    UPS_GetNumValue.apply(USV_Log,USV_OutputV,"OUTPUTV \\: ([-+]?[0-9]*\\.?[0-9]) Volts","LogLine")
    logInfo("USV.Handler","USV Output Volts: "+USV_OutputV.state.toString+" Volt")

    UPS_GetNumValue.apply(USV_Log,USV_Load,"LOADPCT \\: ([-+]?[0-9]*\\.?[0-9]) Percent","LogLine")
    logInfo("USV.Handler","USV Load: "+USV_Load.state.toString+" %")

    UPS_GetNumValue.apply(USV_Log,USV_BattCharge,"BCHARGE \\: ([-+]?[0-9]*\\.?[0-9]) Percent","LogLine")
    logInfo("USV.Handler","USV Battery Charge: "+USV_BattCharge.state.toString+" %")

    UPS_GetNumValue.apply(USV_Log,USV_MBattChg,"MBATTCHG \\: ([-+]?[0-9]*\\.?[0-9]) Percent","LogLine")
    logInfo("USV.Handler","USV Battery Change: "+USV_MBattChg.state.toString+" %")

    UPS_GetNumValue.apply(USV_Log,USV_TimeLeft,"TIMELEFT \\: ([-+]?[0-9]*\\.?[0-9]) Minutes","LogLine")
    logInfo("USV.Handler","USV Time Left: "+USV_TimeLeft.state.toString+" %")

    UPS_GetNumValue.apply(USV_Log,USV_ITemp,"ITEMP \\: ([-+]?[0-9]*\\.?[0-9]) C","LogLine")
    logInfo("USV.Handler","USV Temperatur: "+USV_ITemp.state.toString+" C")

    UPS_GetNumValue.apply(USV_Log,USV_BattVoltage," BATTV \\: ([-+]?[0-9]*\\.?[0-9]) Volts","LogLine")
    logInfo("USV.Handler","USV Battery Volts: "+USV_BattVoltage.state.toString+" Volt")

    UPS_GetStrValue.apply(USV_Log,USV_BattDate,"(?<=BATTDATE : )(.*?)(?= )","LogLine")
    logInfo("USV.Handler","USV Battery Date: "+USV_BattDate.state.toString+"")
end

Script output and content of USV_Log looks like this:
APC : 001,045,1049 DATE : 2016-06-21 13:31:06 +0200 HOSTNAME : lemy VERSION : 3.14.12 (29 March 2014) debian UPSNAME : Aquarium CABLE : USB Cable DRIVER : USB UPS Driver UPSMODE : Stand Alone STARTTIME: 2016-06-20 21:29:20 +0200 MODEL : Back-UPS CS 650 STATUS : ONLINE LINEV : 234.0 Volts LOADPCT : 30.0 Percent BCHARGE : 100.0 Percent TIMELEFT : 19.2 Minutes MBATTCHG : 20 Percent MINTIMEL : 3 Minutes MAXTIME : 0 Seconds OUTPUTV : 230.0 Volts SENSE : Medium DWAKE : 0 Seconds DSHUTD : 0 Seconds LOTRANS : 180.0 Volts HITRANS : 266.0 Volts RETPCT : 0.0 Percent ITEMP : 29.2 C ALARMDEL : 30 Seconds BATTV : 13.6 Volts LINEFREQ : 50.0 Hz LASTXFER : No transfers since turnon NUMXFERS : 0 TONBATT : 0 Seconds CUMONBATT: 0 Seconds XOFFBATT : N/A SELFTEST : NO STESTI : None STATFLAG : 0x05000008 MANDATE : 2004-10-29 SERIALNO : QB0444135240 BATTDATE : 2015-03-18 NOMOUTV : 230 Volts NOMINV : 230 Volts NOMBATTV : 12.0 Volts NOMPOWER : 400 Watts FIRMWARE : 817.v2.I USB FW:v2 END APC : 2016-06-21 13:31:07 +0200
(All in one line.)

I have no idea what I am missing. Maybe someone can help me out…

Thanks a lot and cheers
Roi

Edited the post to English. Sorry, I was somewhere else and somehow thought it is a German forum. :wink:

I see nothing obviously wrong.

Have you logged out sExpr to make sure that you are indeed passing what you think you are passing?

compile is static so the “::” is correct but the error implies that Pattern::compile(String str) doesn’t exist. But it does… :expressionless:

You might try using the fully qualified name for Pattern and see if that works.

Hi and thank you for your reply.

Sorry, I just get in touch with openHAB. What is the fully qualified name for Pattern?

Thanks and cheers,
Roi

java.util.regex.Pattern

Thank you, now I get:

Error during the execution of rule 'USV ATLAN Handling': The name 'java' cannot be resolved to an item or type.

I changed the line to:

ups_pattern = java.util.regex.Pattern::compile(sExpr)

Any idea?

Maybe you need to import it? This is weird as there is nothing obviously wrong with it as originally written. Grasping at straws.

I tried:

import java.util.regex.Pattern

Now:

2016-06-27 12:59:46.533 [ERROR] [o.o.c.s.ScriptExecutionThread ] - Error during the execution of rule 'USV ATLAN Handling': The name '<XFeatureCallImplCustom>.find()' cannot be resolved to an item or type.

No idea… :-/

It works now!!!

Here’s the code (just the first part before the rule definition, which I did not change):

import java.util.regex.*

val org.eclipse.xtext.xbase.lib.Functions$Function4 UPS_GetNumValue = [
    org.openhab.core.library.items.StringItem logITem,
    org.openhab.core.library.items.NumberItem upsITem,
    String sExpr,
    String logline |
        var String ups_log
        ups_log = logITem.state.toString
        var Pattern ups_pattern
        ups_pattern = Pattern::compile(sExpr)
        var Matcher ups_matcher
        ups_matcher = ups_pattern.matcher(ups_log)
        ups_matcher.find()
        var String ups_result
        ups_result = ups_matcher.group(1)
        upsITem.postUpdate(ups_result)
]

val org.eclipse.xtext.xbase.lib.Functions$Function4 UPS_GetStrValue = [
    org.openhab.core.library.items.StringItem logITem,
    org.openhab.core.library.items.StringItem upsITem,
    String sExpr,
    String logline |
        var String ups_log
        ups_log = logITem.state.toString
        var Pattern ups_pattern
        ups_pattern = Pattern::compile(sExpr)
        var Matcher ups_matcher
        ups_matcher = ups_pattern.matcher(ups_log)
        ups_matcher.find()
        var String ups_result
        ups_result = ups_matcher.group(1)
        upsITem.postUpdate(ups_result)
]

Not sure what type of APC you have or what the output of that script looks like but I just made this items file yesterday. My needs are pretty simple, I want to use the external temperature and humidity sensor on the UPS to monitor the environment in my equipment closet. Then turn an exhaust fan I installed in the closet on or off at certain temperatures and/or during times of increased infrastructure activity (specifically CPU, disk IO, or 3+ simultaneously active audio zones). I wasn’t able to find the OID for the humidity but from my testing of the closet humidity isn’t a problem in my case as it draws conditioned air from the hallway. I was able to get the temperature to openhab working so quickly I added other items just for completeness. Powerchut and VMware does a pretty good job at automating shutdown and startup so I’ll probably never actually use these extra items. In any event these items may help you or someone else who finds this thread.

>   Number	SmartUPS2200AdvBatteryCapacity	"UPS Percentage Battery Capacity"	(ALL,AVCloset,UPS,battery)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.2.2.1.0:10000]"}
> Number	SmartUPS2200AdvBatteryTemperature	"UPS Battery Temperature [%.1f °F]"	<temperature>	(ALL,AVCloset,UPS)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.2.2.2.0:60000]"}
> Number	SmartUPS2200AdvBatteryRunTimeRemaining	"Time remaining on battery"	(ALL,AVCloset,UPS,battery)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.2.2.3.0:10000]"}
> Number	SmartUPS2200AdvInputLineVoltage	"UPS Main Input Voltage"	(ALL,AVCloset,UPS)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.3.2.1:10000]"}
> Number	SmartUPS2200AdvOutputVoltage	"UPS Main Output Voltage"	(ALL,AVCloset,UPS)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.4.2.1.0:10000]"}
> Number	SmartUPS2200AdvOutputLoad	"UPS Output Load Percentage"	(ALL,AVCloset,UPS)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.4.2.3.0:10000]"}
> Number	SmartUPS2200PhaseInputVoltage	"UPS Phase 1 Input Voltage"	(ALL,AVCloset,UPS)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.9.2.3.1.3.1.1.1:10000]"}
> Number	SmartUPS2200PhaseOutputVoltage	"UPS Phase 1 Output Voltage"	(ALL,AVCloset,UPS)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.9.3.3.1.3.1.1.1:10000]"}
> Number	SmartUPS2200PhaseOutputPercentLoad	"UPS Output Load Percentage"	(ALL,AVCloset,UPS)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.9.3.3.1.10.1.1.1:10000]"}
> Number	SmartUPS2200Temperature	"UPS Temperature [%.1f °F]"	<temperature>   (ALL,ff,AVCloset)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.10.2.3.2.1.4.1:60000]"}

> Number	SmartUPS2200AdvBatteryNumOfBattPacks	(ALL,AVCloset,UPS,battery)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.2.2.5:10000]"}
> Number	SmartUPS2200AdvBatteryReplaceIndicator	(ALL,AVCloset,UPS,battery)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.2.2.4.0:10000]"}	//Indicates whether the UPS batteries need replacing.
> Number	SmartUPS2200AdvInputMaxLineVoltage	(ALL,AVCloset,UPS)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.3.2.2:10000]"}	
> Number	SmartUPS2200AdvOutputFrequency	(ALL,AVCloset,UPS)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.4.2.2:10000]"}		//The current output frequency of the UPS system in Hz.
> Number	SmartUPS2200BasicBatteryTimeOnBattery	(ALL,AVCloset,UPS,battery)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.2.1.2:10000]"}	//The elapsed time since the UPS has switched to battery power.
> Number	SmartUPS2200AdvBatteryRunTimeRemaining1	(ALL,AVCloset,UPS,battery)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.2.2.3:10000]"}	//The UPS battery run time remaining before battery exhaustion.
> Number	SmartUPS2200AdvInputLineFailCause	(ALL,AVCloset,UPS)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.3.2.5:60000]"}	//The reason for the occurrence of the last transfer to UPS battery power. The variable is set to: - noTransfer(1) -- if there is no transfer yet. - highLineVoltage(2) -- if the transfer to battery is caused by an over voltage greater than the high transfer voltage. - brownout(3) -- if the duration of the outage is greater than five seconds and the line voltage is between 40% of the rated output voltage and the low transfer voltage. - blackout(4) -- if the duration of the outage is greater than five seconds and the line voltage is between 40% of the rated output voltage and ground. - smallMomentarySag(5) -- if the duration of the outage is less than five seconds and the line voltage is between 40% of the rated output voltage and the low transfer voltage. - deepMomentarySag(6) -- if the duration of the outage is less than five seconds and the line voltage is between 40% of the rated output voltage and ground. The variable is set to - smalNumber	
> Number	SmartUPS2200upsAdvIdentFirmwareRevision	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.1.2.1:10000]"}	//The firmware revision of the UPS system's microprocessor.
> Number	SmartUPS2200AdvIdentSerialNumber	(ALL,AVCloset,UPS)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.1.2.3:60000]"}		//An 8-character string identifying the serial number of the UPS internal microprocessor. This number is set at the factory. NOTE: This number does NOT correspond to the serial number on the rear of the UPS.
> String	SmartUPS2200BasicIdentName	(ALL,AVCloset,UPS)	{snmp="<[192.168.1.10:public:.1.3.6.1.2.1.1.5.0:60000]"}	//An 8 byte ID string identifying the UPS. This object can be set by the administrator.
> Number	SmartUPS2200AdvInputMinLineVoltage	(ALL,AVCloset,UPS)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.3.2.3:10000]"}	//The minimum utility line voltage in VAC over the previous 1 minute period.
> Number	SmartUPS2200AdvBatteryNumOfBadBattPacks	(ALL,AVCloset,UPS,battery)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.2.2.6:10000]"}	//The number of external battery packs connected to the UPS that are defective. If the UPS does not use smart cells then the agent reports ERROR_NO_SUCH_NAME.
> Number	SmartUPS2200AdvInputFrequency	(ALL,AVCloset,UPS)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.3.2.4:10000]"}	//The current input frequency to the UPS system in Hz.
> Number	SmartUPS2200AdvOutputCurrent	(ALL,AVCloset,UPS)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.4.2.4:10000]"}	//The current in amperes drawn by the load on the UPS.
> Number	SmartUPS2200BasicBatteryLastReplaceDate	(ALL,AVCloset,UPS,battery)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.2.1.3:60000]"}	//The date when the UPS system's batteries were last replaced in mm/dd/yy format. For Smart-UPS models , this value is originally set in the factory. When the UPS batteries are replaced , this value should be reset by the administrator.
> Number	SmartUPS2200BasicOutputStatus	(ALL,AVCloset,UPS)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.4.1.1:10000]"}	//The current state of the UPS. If the UPS is unable to determine the state of the UPS this variable is set to unknown(1).
> Number	SmartUPS2200BasicBatteryStatus	(ALL,AVCloset,UPS,battery)	{snmp="<[192.168.1.10:public:1.3.6.1.4.1.318.1.1.1.2.1.1:10000]"}	//The status of the UPS batteries. A batteryLow(3) value indicates the UPS will be unable to sustain the current load, and its services will be lost if power is not restored. The amount of run time in reserve at the time of low battery can be configured by the upsAdvConfigLowBatteryRunTime.

Don’t know why but the forum formatting added a “>” before all the items. Obviously you’ll have to remove those.

Hi Kerwin,

thanks a lot! I’m sure this will be of use for lots of people dealing with UPS devices. For me it is not relevant at the moment as my UPS devices only have USB connection, no LAN interface. But I will keep this in mind for the next generation of UPS devices here. :wink:

Cheers,
Roi

Hello everybody, again…

I just upgraded to openHAB 2 and the rule does not work anymore. Now I get this error in the log:

2017-04-21 12:01:36.302 [ERROR] [.script.engine.ScriptExecutionThread] - Rule ‘USV Handling’: The name ‘.state’ cannot be resolved to an item or type.

The error is there for both versions of the code (post 1 and my modified version in post 9). As I am really stuck here I would be very happy if someone can take a look and maybe point me into the right direction.

Thank you, regards,
Roi

The error implies that you have a reference to an Item that doesn’t exist or perhaps is NULL. Look very carefully at all the Item names and make sure there isn’t a typo. Designer is great for checking syntax. Then check the states of all the referenced items. If any are NULL (will show up as “-” on the sitemap) that might be a problem.

I’m grasping at straws. If it worked in OH 1.x is should work in 2.x as written.

Thank you, I think you and the Designer put me on the right path.

I had to change org.openhab.core.library.items into org.eclipse.smarthome.core.library.items - now it works again! :slight_smile:

This is pointed out in the migration tutorial that you may have missed.
In fact
org.openhab.core.library.items.StringItem xxx
should become
StringItem xxx
in OH2

@rossko57 Thank you for bringing that to my attention. I did miss that, but I saw the page weeks ago… I’m sure I missed more stuff as I do not get the old S300TH binding up and running…

Hi All,

here is a little bit more generic solution for openhab2:

My script apc_dump_log just dumping only the requested key/value pairs (json):

#!/usr/bin/python

import json
import subprocess # needed for apcaccess call

request = ('TONBATT' , 'BCHARGE' , 'STATUS' , 'LINEV', 'LOADPCT', 'BATTV' , 'TIMELEFT' ,'MBATTCHG' , 'MINTIMEL' , 'LASTXFER', 'DATE')
# include those for debugging so the openhab rule fires on every reading
#     , 'DATE' ,  'END APC' )

result = {}

# get status from apc-ups
res = subprocess.check_output("/sbin/apcaccess")
for line in res.split('\n'):
    (key,spl,val) = line.partition(': ')
    key = key.rstrip()   # .lower()
    if key in request :
        val = val.strip()
        result[key] = val

print(json.dumps(result, indent=0))

Here is my item file apc.items:

/* APC UPS */

Group APC  (All)
Group APC_Values  (All)

String APCRaw "APC: [%s]"  {channel="exec:command:apc:output"}
DateTime APCLastExecution "Status am: [%1$tA, %1$td.%1$tm.%1$tY %1$tH:%1$tM]" (APC) {channel="exec:command:apc:lastexecution"}

String APC_STATUS	"STATUS   [%s]"	(APC,APC_Values)
String APC_LINEV	"LINEV    [%s]"	(APC,APC_Values)
String APC_LOADPCT	"LOADPCT  [%s]"	(APC,APC_Values)
String APC_BATTV	"BATTV    [%s]"	(APC,APC_Values)
String APC_BCHARGE	"BCHARGE  [%s]"	(APC,APC_Values)
String APC_TIMELEFT	"TIMELEFT [%s]"	(APC,APC_Values)
String APC_MBATTCHG	"MBATTCHG [%s]"	(APC,APC_Values)
String APC_MINTIMEL	"MINTIMEL [%s]"	(APC,APC_Values)
String APC_TONBATT	"TONBATT  [%s]"	(APC,APC_Values)
String APC_LASTXFER	"LASTXFER [%s]"	(APC,APC_Values)

my apc.rules:

rule "APC UPS Handling"
when
   Item APCRaw changed
then
   val String json = (APCRaw.state as StringType).toString

   APC_Values.members.forEach [ value |
      var String name = value.name.replace('APC_','$.')
      value.postUpdate(transform("JSONPATH", name, json).replaceAll('"',''))
   ]
end

and finally my exec.things:

Thing exec:command:apc [command="/etc/openhab2/scripts/apc_dump_log", interval=60, timeout=5]

Ally ou need to add to your sitemap is a group item:

	Group item=APC label="APC USV" icon="energy"

You need the exec binding and the jsonpath transformation. Of course more time could be invested in beautifying the items, parsing numbers etc. But still I hope the approach may help one or the other.

Happy hacking
Karl

3 Likes

@jo47011, very nice solution! :slight_smile: Thank you!

I need to take a closer look into it some time. At the moment I am focusing on getting everything up and running in OH2 and doing some documentation as I forget stuff without it. :wink: I put my current APC UPS solution for OH2 into my Git repo, here it is, maybe also interesting for others: https://git.hot-chilli.net/msebald/openhab/src/master/apcups

Thanks for posting the method to access apcupsd - it worked right out of the box for me. :grinning:

For my setup, I simplified it a bit and created a single Thing with an associated Item for each of the parameters I’m interested in for the UPS (example for STATUS below). I monitor several parameters and each one can be done at different time intervals (or triggered by rules? but not there yet).

But clearly parsing the full output of apcaccess is what’s needed for a solid binding so thanks for sharing. :+1:

ups.things:
Thing exec:command:UPSstatus [command="/sbin/apcaccess -p STATUS", interval=60, timeout=2]

ups.items:
String UPS_Status “UPS Status: [%s]” (gUPS) {channel=“exec:command:UPSstatus:output”}