APC UPS Backup - Now just needs a plugin

The new Vera3 comes with an integrated battery backup, which is awesome except your modem, all your z-wave devices, wifi, etc is still going to go down. You need a real battery backup that will stay online long enough to fire off notifications that your net is down.

http://www.amazon.com/dp/B001BTWQDS/

I picked up an el cheapo APC UPS ES 450. It has 4 battery backup ports and advertises 30 minutes in a power outage. It has a funny USB to Ethernet cable (the Ethernet end hooks up to the APC), so you can control and monitor it from the Vera2/3. However, the kernel device does not exist on the Vera and you have to install that. You’ll also need to install the monitoring daemon. To my knowledge the Vera2 hardware is the same so you’ll have to go through these steps:

1.) Log into your Vera as root.

2.) Install the USB-HID kernel object. You don’t need to compile one or anything - you can install this one that’s close enough.

http://www.dd-wrt.com/phpBB2/viewtopic.php?t=65676
http://www.dd-wrt.com/phpBB2/viewtopic.php?p=182132

Here’s what I did:

root@MiOS_18273:/# cd /lib/modules/2.4.37.9
root@MiOS_18273:/lib/modules/2.4.37.9# wget "http://www.dd-wrt.com/phpBB2/download.php?id=5266" -O hid.o.tgz
Connecting to www.dd-wrt.com (83.141.4.210:80)
hid.o.tgz            100% |*********************************************************************************************************************************************************************************************| 14293  00:00:00 ETA
root@MiOS_18273:/lib/modules/2.4.37.9# tar xzvf hid.o.tgz
hid.o
root@MiOS_18273:/lib/modules/2.4.37.9# insmod -f hid.o
root@MiOS_18273:/lib/modules/2.4.37.9# cat /proc/bus/usb/drivers
         usbdevfs
         hub
         acm
         serial
         usb-storage
 [b]96-111: hiddev[/b]
         hid
root@MiOS_18273:/lib/modules/2.4.37.9# cat /proc/bus/usb/devices | grep HID
I:  If#= 0 Alt= 0 #EPs= 1 Cls=03(HID  ) Sub=00 Prot=00 Driver=[b]hid[/b]

3.) Now that the HID device is installed, we need to install the daemon

opkg install "http://downloads.openwrt.org/backfire/10.03/brcm-2.4/packages/apcupsd_3.14.7-1_brcm-2.4.ipk"

Refer to http://www.apcupsd.org/ for more info on how to interact with the device.

4.) Modify your config file. Its in /etc/apcupsd/apcupsd.conf
5.) Check to make sure it’s running:

root@MiOS_18273:/lib/modules/2.4.37.9# apcaccess status
APC      : 001,035,0892
DATE     : Wed Sep 14 20:53:47 CDT 2011
HOSTNAME : MiOS_18273
VERSION  : 3.14.7 (1 August 2009) unknown
UPSNAME  : ups1
CABLE    : USB Cable
MODEL    : Back-UPS ES 450
UPSMODE  : Stand Alone
STARTTIME: Wed Sep 14 20:52:44 CDT 2011
STATUS   : ONLINE
LINEV    : 126.0 Volts
LOADPCT  :   0.0 Percent Load Capacity
BCHARGE  : 100.0 Percent
TIMELEFT : 126.0 Minutes
MBATTCHG : 5 Percent
MINTIMEL : 3 Minutes
MAXTIME  : 0 Seconds
SENSE    : Medium
LOTRANS  : 092.0 Volts
HITRANS  : 139.0 Volts
ALARMDEL : Always
BATTV    : 13.5 Volts
LASTXFER : Unacceptable line voltage changes
NUMXFERS : 0
TONBATT  : 0 seconds
CUMONBATT: 0 seconds
XOFFBATT : N/A
STATFLAG : 0x07000008 Status Flag
MANDATE  : 2011-04-09
SERIALNO : 3B1115X42707
BATTDATE : 2011-04-09
NOMINV   : 120 Volts
NOMBATTV :  12.0 Volts
FIRMWARE : 844.K2 .D USB FW:K2
APCMODEL : Back-UPS ES 450
END APC  : Wed Sep 14 20:53:49 CDT 2011

If anyone wants to attempt to write the plugin with me please let me know.

Thanks,

Kristopher

Let’s do it.

Although I’ve got one of these, it isn’t in the same room as my Vera, so you’ll have to be the sole tester. (Also, I use nut, because I have multiple brands of UPS in my house.)

I propose that we start simple and make the plugin appear as an appliance switch. On == on A/C power; Off == on battery. Will that do for a beginning?

@futile,
If your able to manually switch from mains to battery via the API then a switch makes sense. Otherwise, if you can only detect what state it’s in you might want to used LightSensor1 instead, since it’s inherently read-only… And will have a correct rendition on conol points…

A lightsensor would make more sense. You can use the % value of the light sensor to show the remaining percentage left of the battery when on battery power. My vera is hooked up to a cyperpower ups in the garage which a switch is also plugged into the cyberpower as well. Is available via dd-wrt repository? I have a few apc ups, however they are used in my office which is several floors up from the vera unit.

  • Garrett

The power loss detection should be event driven.

Excerpt from the excellent apcupsd manual:

Apcupsd Notification and Events
When a major event is generated within apcupsd, control is passed to the script apccontrol normally found
in /etc/apcupsd/apccontrol. The event name, and a number of other important parameters are passed to
the script.

I’m sure there are many other ways to get the UPS events to the Luup plugin.

In addition, the plugin could poll the current UPS status and store the UPS variables in corresponding UPnP variables.
Add Luup events to the plugin as needed.

BTW, is there an UPnP service for UPS from the UPnP Forum?

Absolutely. (I was going to ask about that later, to avoid cognitive overload now.) It’s good to see that it’s possible.

BTW, is there an UPnP service for UPS from the UPnP Forum?

I did a quick search; not that I could see. link

There’s no reason why we can’t tap into more than one service. The trouble with the standard light sensor service is that there’s no binary event that it can trigger, corresponding to “lost AC power” and “regained AC power”. If we make the plugin set both LightSensor and SwitchPower variables, then users can use the device how they see fit.

We can control the dashboard appearance independently of this decision.

The plugin should display STATUS, LINEV, LOADPCT, BCHARGE, TIMELEFT, BATTV. It could use red color for the values if outside the nominal values (MBATTCHG, MINTIMEL, NOMINV, NOMBATTV).

… and some suggestions for the GUI presentation.

Awesome guys, love the input. Will get started on the code tonight.

Perfect timing. :slight_smile: My UPS ES 700 will arrive tomorrow.

BTW, OUTPUTV (“The voltage the UPS is supplying to your equipment”) should be visible in the GUI, too.

Let us know how we can help, whether it’s the Lua code or the XML files.

BTW, OUTPUTV ("The voltage the UPS is supplying to your equipment") should be visible in the GUI, too.

Still trying to see how to do this. Any ideas?

Still trying to see how to do this. Any ideas?

OK I see now. My low-end unit doesn’t support this. No worries though, I should be able to implement for when its available.

OK I’m getting near the part where I need help. I really don’t know best practices for setting up devices and what the json should look like. I have, however, completely the polling of the apcaccess device. Here is the snippet:

[code]function refreshCache()

    debug("refreshCache called")
    luup.call_timer("refreshCache", 1, "30s", "")

    local pipe = io.popen("apcaccess")
    local status = pipe:read("*all")
    pipe:close()
    log("APC #" .. status)

    if (status ~= nil) then
      debug("Successful execution of apcaccess")

      local currentStatus = string.match(status, "STATUS%s*:%s*([A-Z0-9\. ]+)")
      local currentLinev = string.match(status, "LINEV%s*:%s*([0-9\.]+)")
      local currentLoadpct = string.match(status, "LOADPCT%s*:%s*([0-9\.]+)")
      local currentBcharge = string.match(status, "BCHARGE%s*:%s*([0-9\.]+)")
      local currentBattv = string.match(status, "BATTV%s*:%s*([0-9\.]+)")
      local currentTimeleft = string.match(status, "TIMELEFT%s*:%s*([0-9\.]+)")
      local currentOutputv = string.match(status, "OUTPUTV%s*:%s*([0-9\.]+)")

    log("APC #" .. currentStatus .. " " .. currentLinev .. " " .. currentLoadpct .. " " .. currentBcharge .. " " .. currentBattv .. " " .. currentTimeleft)

      -- Store the current status
      luup.variable_set("urn:micasaverde-com:serviceId:LightSensor1", "CurrentLevel",
          currentLoadpct, CURRENT_APC_DEVICE)
      luup.variable_set("urn:micasaverde-com:serviceId:LightSensor1", "CurrentVoltage",
          currentLinev, CURRENT_APC_DEVICE)
      luup.variable_set("urn:micasaverde-com:serviceId:LightSensor1", "Status",
          currentStatus, CURRENT_APC_DEVICE)
      luup.variable_set("urn:micasaverde-com:serviceId:LightSensor1", "BatteryLevel",
          currentBcharge, CURRENT_APC_DEVICE)
      luup.variable_set("urn:micasaverde-com:serviceId:LightSensor1", "BatteryDate",
          os.time(), CURRENT_APC_DEVICE)
      luup.variable_set("urn:micasaverde-com:serviceId:LightSensor1", "BatteryVoltage",
          currentBattv, CURRENT_APC_DEVICE)
      luup.variable_set("urn:micasaverde-com:serviceId:LightSensor1", "TimeLeft",
          currentTimeleft, CURRENT_APC_DEVICE)
      luup.variable_set("urn:micasaverde-com:serviceId:LightSensor1", "Watts",
          currentOutputv, CURRENT_APC_DEVICE)



    else
      log("APC returned status=" .. status)
    end

    luup.variable_set("urn:micasaverde-com:serviceId:HaDevice1", "LastUpdate",
          os.time(), PARENT_DEVICE)
end

[/code]

This works by basically just piping the contents of “apcaccess” into a string, then regexing out the appropriate info. I wrote some stuff into a light sensor as per Futzle’s suggestion. I have no idea how to make those cool battery icons. I can make the pattern matching more robust if need be (Outputv, for example, does not exist on my device).

If someone at a high level can sketch out the proper way to do the device let me know. For my AirMonitor plugin I created a new device for each metric and it was really not fun to do.

PS: I set the “Watts” to Outputv, which is not correct. It should be Outputv * whatever the current is. However, I don’t currently know what that is.

Just for reference (status ONLINE):

APC      : 001,035,0895
DATE     : Fri Sep 23 23:11:37 CEST 2011
HOSTNAME : MiOS_XXXXX
VERSION  : 3.14.7 (1 August 2009) unknown
UPSNAME  : UPS_ES_700
CABLE    : USB Cable
MODEL    : Back-UPS ES 700G
UPSMODE  : Stand Alone
STARTTIME: Fri Sep 23 23:11:35 CEST 2011
STATUS   : ONLINE
LINEV    : 232.0 Volts
LOADPCT  :   0.0 Percent Load Capacity
BCHARGE  : 100.0 Percent
TIMELEFT :  43.8 Minutes
MBATTCHG : 5 Percent
MINTIMEL : 3 Minutes
MAXTIME  : 0 Seconds
SENSE    : Medium
LOTRANS  : 180.0 Volts
HITRANS  : 266.0 Volts
ALARMDEL : Always
BATTV    : 13.6 Volts
LASTXFER : No transfers since turnon
NUMXFERS : 0
TONBATT  : 0 seconds
CUMONBATT: 0 seconds
XOFFBATT : N/A
STATFLAG : 0x07000008 Status Flag
MANDATE  : 2011-05-18
SERIALNO : 5
BATTDATE : 2011-05-18
NOMINV   : 230 Volts
NOMBATTV :  12.0 Volts
FIRMWARE : 871.O2 .I USB FW:O2
APCMODEL : Back-UPS ES 700G
END APC  : Fri Sep 23 23:11:53 CEST 2011

LOADPCT and TIMELEFT are useless for status ONLINE.

Output for status ONBATT:

APC      : 001,036,0929
DATE     : Fri Sep 23 23:19:37 CEST 2011
HOSTNAME : MiOS_XXXXX
VERSION  : 3.14.7 (1 August 2009) unknown
UPSNAME  : UPS_ES_700
CABLE    : USB Cable
MODEL    : Back-UPS ES 700G
UPSMODE  : Stand Alone
STARTTIME: Fri Sep 23 23:11:35 CEST 2011
STATUS   : ONBATT
LINEV    : 000.0 Volts
LOADPCT  :   4.0 Percent Load Capacity
BCHARGE  : 087.0 Percent
TIMELEFT :  33.5 Minutes
MBATTCHG : 5 Percent
MINTIMEL : 3 Minutes
MAXTIME  : 0 Seconds
SENSE    : Medium
LOTRANS  : 180.0 Volts
HITRANS  : 266.0 Volts
ALARMDEL : Always
BATTV    : 12.7 Volts
LASTXFER : Low line voltage
NUMXFERS : 1
XONBATT  : Fri Sep 23 23:19:17 CEST 2011
TONBATT  : 24 seconds
CUMONBATT: 24 seconds
XOFFBATT : N/A
STATFLAG : 0x07060010 Status Flag
MANDATE  : 2011-05-18
SERIALNO : 5
BATTDATE : 2011-05-18
NOMINV   : 230 Volts
NOMBATTV :  12.0 Volts
FIRMWARE : 871.O2 .I USB FW:O2
APCMODEL : Back-UPS ES 700G
END APC  : Fri Sep 23 23:19:41 CEST 2011

No OUTPUTV ,

IMHO you should create a new service (e.g., urn:kristopher-com:serviceId:UPS1) for all UPS variables that don’t match the predefined variables ([tt]http://wiki.micasaverde.com/index.php/Luup_UPNP_Files[/tt]) for a light sensor.

Your UPS device should consist of two devices: one predefined device (for compatibility with non-UIx user interfaces) and a specialized UPS device.

GUI presentation (quick and dirty):

Install WAI ([tt]http://forum.micasaverde.com/index.php?topic=6441.0[/tt])

local message = "APC #" .. currentStatus .. " " .. currentLinev .. " " .. currentLoadpct .. " " .. currentBcharge .. " " .. currentBattv .. " " .. currentTimeleft
luup.variable_set( 'urn:upnp-ap15e-com:serviceId:WAI1', 'Location', message, WAI_dev_ID )

Derive your device files from WAI and GWC. Copy with pride. :slight_smile:

GWC uses HTML injection to display icons and formatted text in the GUI. I don’t know whether there is a way without using HTML injection which - of course - isn’t politically correct (but doesn’t break in UI5 …).

No idea where to get the icons (they must be freely usable).
Consult the dataMine plugin for information about where to put the icons.

[quote=“Kristopher, post:14, topic:169161”]I really don’t know best practices for setting up devices and what the json should look like. I have, however, completely the polling of the apcaccess device. Here is the snippet:
[…]
I wrote some stuff into a light sensor as per Futzle’s suggestion. I have no idea how to make those cool battery icons. […] If someone at a high level can sketch out the proper way to do the device let me know.[/quote]

That’s a pretty good start. Here’s a few recommendations:

It’s allowed (I’d even say it’s desirable) to use multiple service IDs in your device. Pick and choose from among the standard services for things that match up. For stuff that doesn’t match any existing service, invent your own service ID. For stuff that does match an existing service (LightSensor has CurrentLevel, for example), set the variable twice: once with your service ID and once with the out-of-the-box service ID. Examples:

-- Set battery level.  First is for my code to use, second is for battery icon in dashboard.
-- (The string urn:micasaverde-com:serviceId:HaDevice1 is magical.)
luup.variable_set("urn:kristopher:serviceId:UPS1", "BatteryLevel",
      currentBcharge, CURRENT_APC_DEVICE)
luup.variable_set("urn:micasaverde-com:serviceId:HaDevice1", "BatteryLevel",
      currentBcharge, CURRENT_APC_DEVICE)
-- Set load.  First is for my code to use, second is to masquerade as a light sensor.
luup.variable_set("urn:kristopher:serviceId:UPS1", "CurrentLevel",
      currentLoadpct, CURRENT_APC_DEVICE)
luup.variable_set("urn:micasaverde-com:serviceId:LightSensor1", "CurrentLevel",
      currentLoadpct, CURRENT_APC_DEVICE)
-- Set online status.  First is to masquerade as a binary switch, second is for my code to use.
luup.variable_set("urn:upnp-org:serviceId:SwitchPower1", "Status",
      (currentStatus == "ONLINE") and 1 or 0, CURRENT_APC_DEVICE)
luup.variable_set("urn:kristopher:serviceId:UPS1", "Status",
      currentStatus, CURRENT_APC_DEVICE)
-- No common service knows of this type.  Just set the variable in my namespace.
luup.variable_set("urn:kristopher:serviceId:UPS1", "CurrentVoltage",
      currentLinev, CURRENT_APC_DEVICE)

As Ap15e says, everyone just copies-and-pastes device files. My usual sequence when making a brand-new device is:

  1. Write a stub D_APCUPSBackup1.xml file. Copy and paste it from an existing device, changing: deviceType, ImplementationFile. Empty out the serviceList. Point staticJson to a not-yet-existing D_APCUPSBackup1.json file. We’ll make that later.

  2. Write a simple I_APCUPSBackup1.xml file. Put your Lua in it. Leave most of the other sections empty.

(By this point you will be able to create a device from this file, see its log output in the Luup log, and see its variables in the Advanced tab. You can debug your Lua until you are happy that the variables are getting set properly. The dashboard will have a default device box, and you won’t have any events or notifications.)

  1. Write the D_APCUPSBackup1.json Static JSON file. Again, copy and paste from an existing device, or read my reverse-engineering notes on the wiki. Leave eventList and sceneList empty for the moment. While debugging, if you need to make changes to the Static JSON file, you need to restart the Luup engine and clear your browser’s cache.

(By this point you will have a customized device appearance in the dashboard. Whatever status you’ve opted to include in the dashboard will update every thirty seconds, and if you’ve defined any tabs in the static JSON file, they’ll appear too.)

If all you care about is read-only viewing of the UPS status, you can stop now.

  1. Define events in the static Json file. I haven’t written the reverse-engineering notes for this section yet, so copy from something already present, or let us help you write them. Events are what can trigger scenes and notifications.

(By this point you will have the ability for the plugin to trigger a scene when, say, the UPS goes onto battery, or when the load goes over a certain percentage, or whatever other events you’ve defined.)

  1. Go back to the D_APCUPSBackup1.xml file and fill out the serviceList section. For each service Id you’ve ever mentioned, put in a service section. If you follow my examples above you’ll have four. Three will mention S_*.xml files that already exist. For the fourth, invent a name (say, S_UPS1.xml) and match it against your urn:kristopher:serviceId:UPS1 service Id.

(You won’t notice anything different in the UI, but anyone using “watch variable” or other introspection will be able to act on your status variables changing.)

  1. Write the S_UPS1.xml file. Include any variables that you have mentioned in the urn:kristopher:serviceId:UPS1 service namespace. If you want to define any actions (for the UPS, acting on an interrupt when the UPS suddenly goes off battery would be a good one), put them in.

(Again, no UI difference, but now you can interact with the device over HTTP, and things like the dataMine plugin will be able to plot your device’s variables over time.)

LOADPCT and TIMELEFT are useless for status ONLINE.

I suspect TIMELEFT is probably very dynamic based on current load, and also that the battery probably loses TIMELEFT over time. So it might not be entirely useless on wall power. Also, LOADPCT is a non-100% value after an outage. Might be useful for taking action during a brownout or rolling blackout.

Kristopher

I suspect that the UPS doesn’t measure LOADPCT if STATUS == ONLINE.

May I suggest using a generic ‘apcupsd to UPnP’ mapping?

local st = 'APC      : 001,035,0895\n'..
'DATE     : Fri Sep 23 23:11:37 CEST 2011\n'..
'HOSTNAME : MiOS_XXXXX\n'..
'VERSION  : 3.14.7 (1 August 2009) unknown\n'..
'UPSNAME  : UPS_ES_700\n'..
'CABLE    : USB Cable\n'..
'MODEL    : Back-UPS ES 700G\n'..
'UPSMODE  : Stand Alone\n'..
'STARTTIME: Fri Sep 23 23:11:35 CEST 2011\n'..
'STATUS   : ONLINE\n'..
'LINEV    : 232.0 Volts\n'..
'LOADPCT  :   0.0 Percent Load Capacity\n'..
'BCHARGE  : 100.0 Percent\n'..
'TIMELEFT :  43.8 Minutes\n'..
'MBATTCHG : 5 Percent\n'..
'MINTIMEL : 3 Minutes\n'..
'MAXTIME  : 0 Seconds\n'..
'SENSE    : Medium\n'..
'LOTRANS  : 180.0 Volts\n'..
'HITRANS  : 266.0 Volts\n'..
'ALARMDEL : Always\n'..
'BATTV    : 13.6 Volts\n'..
'LASTXFER : No transfers since turnon\n'..
'NUMXFERS : 0\n'..
'TONBATT  : 0 seconds\n'..
'CUMONBATT: 0 seconds\n'..
'XOFFBATT : N/A\n'..
'STATFLAG : 0x07000008 Status Flag\n'..
'MANDATE  : 2011-05-18\n'..
'SERIALNO : 5\n'..
'BATTDATE : 2011-05-18\n'..
'NOMINV   : 230 Volts\n'..
'NOMBATTV :  12.0 Volts\n'..
'FIRMWARE : 871.O2 .I USB FW:O2\n'..
'APCMODEL : Back-UPS ES 700G\n'..
'END APC  : Fri Sep 23 23:11:53 CEST 2011\n'

for identifier,cur_val in string.gmatch( st, '(%w+).-:(.-)\n' )
 do

  print( identifier, cur_val )

 local value = string.match( cur_val, '[0-9.,-]+' )

--  luup.variable_set("urn:kristopher-com:serviceId:UPS1", tostring( identifier ), tostring (cur_val), CURRENT_APC_DEVICE )

  if value ~= nil
   then

	print( '************ ',value )

--    luup.variable_set("urn:kristopher-com:serviceId:UPS1", tostring( identifier ) .. '_VALUE', tostring ( value ), CURRENT_APC_DEVICE )

   end

 end

Not perfect, but I could live with it …

Instructions thus far:

1.) Download archive
2.) Extract images into /www/icons
3.) Upload 4 xml/json files via Luup
4.) Create device D_APC.xml

I would like to figure out some way to script an install file. The installer would need to:

1.) Download the hid.o device into the kernel module directory
2.) echo “hid” > /etc/modules/33-hid
3.) opkg update; opkg install apcupsd
4.) create init script for apcupsd (/etc/rc.*/)
5.) mkdir /www/icons
6.) Download icons from theme with the new filenames

Give it a shot, let me knwo what you think. Very basic but very functional.

Kristopher