APC’s BackUPS HS 500 UPS has been around forever, as has it’s firmware. Still, a wall-mountable network connected UPS with three switchable outputs for less than £100 from one of the most reputable brands seems like a good deal to me.
There are issues with this product though. The web interface doesn’t work on any recent browser and it’s also impossible to configure it without Windows 2000 (XP if you’re lucky). So graceful shutdown is a non-starter then.
Having suffered a few prolonged power-outages recently I thought perhaps it’s time this problem was solved. Fortunately, Anton Bagayev has posted on Github a script to control the outlets from Linux, using Curl to interact with it’s primitive web interface, and this can easily be adapted into a shutdown script – I’ve called this check-power and have it running via cron every minute. When the UPS is on battery and run-time goes to 13 minutes or less, it calls some other shutdown script as needed:
#!/bin/bash # This script uses the web interface of the APC BackUPS HS-500 to check it's status, and # calls some other script to effect a host shutdown, should the UPS be on battery and the # runtime be less than 13 minutes.
# temp file for operational status - battery level etc STATUS="/tmp/apc-500-status.tmp" UPS="[ip-address-goes-here]" # get output values from web-control curl -sl "http://$UPS/status.cgi" | tr -dc '[:print:]\n' > $STATUS
# Extract the unit operating status fields - battery level etc LOAD="$(cat $STATUS | grep -o '[0-9]* Watts' | grep -o '[0-9]*')" BATTERYLEVEL="$(cat $STATUS | grep -o '[0-9]* %' | grep -o '[0-9]*')" RUNTIME="$(cat $STATUS | grep -o '[0-9]* minutes' | grep -o '[0-9]*')" BATTERYSTATUS="$(cat $STATUS | egrep -o 'Charged|Charging|Discharged|Discharging')" UPSSTATUS="$(cat $STATUS | egrep -o 'On Line|On Battery' | sed 's/ / /g')" LASTTEST="$(cat $STATUS | egrep -o 'Result of last self-test is:.*(Passed|Failed)</font>' | egrep -o '(Passed|Failed)')" LASTTRANSFER="$(cat $STATUS | egrep -o 'No Transfer|Blackout' | sed 's/ / /g')"
# show active configuration logger "UPS Status $UPSSTATUS, $RUNTIME minutes remaining (load: $LOAD Watts)" if [ "$UPSSTATUS" == "On Battery" ]; then if [ $RUNTIME -le 13 ]; then logger "UPS Critical: $RUNTIME minutes remaining. Starting shutdown procedure." [call-shutdown-script-goes-here] fi fi
# garbage collector rm -f $STATUS
OK so now we can control it with Anton’s script – the three outputs are individually switchable – and monitor it with this script, but what about configuration? Firing up a Windows 2000 VM and grabbing some Wireshark captures from the supplied configuration utility (really APC?), this is pretty straightforward too. The utility interacts with the UPS via UDP broadcast with some special command codes to make it do things like set the IP.
With a bit of fiddling, this too can be scripted Linux with a few dependencies (arping, xxd, socat). I’ve called this apc500.sh, and it can set the IP address and name of the device from the Linux command line, and uses Anton’s apc.sh to show device status (which can be modified per the above script to add battery levels etc if required):
#!/bin/bash
# UPS Management script for APC 500 HS # # -f - to Find and show detail of the device # -s - to Set the IP address of the device (0.0.0.0 for DHCP) # -n - to set the Name of the device
# Command line parameters - what are we doing?
for i in "$@" do case $i in -f*|--find*) FUNCTION="FIND" ;; -s=*|--setip=*) FUNCTION="SETIP" IPADDRESS="${i#*=}" shift # past argument=value ;; -n=*|--setname=*) FUNCTION="SETNAME" NAME="${i#*=}" shift # past argument=value ;; *) # unknown option ;; esac done
if [ "$FUNCTION" = "FIND" ]; then # Find UPS via broadcast DATA="$(echo '11 50 00 A0 10 50 43 43' | xxd -r -p | socat - UDP4-DATAGRAM:255.255.255.255:9950,so-broadcast,sourceport=9951 | tr -dc '[:print:]\n')" MODEL=${DATA:9:15} SERIAL=${DATA:24:12} MACADD=${DATA:37:12} TMP=${MACADD,,} MACADDR=${TMP:0:2}:${TMP:2:2}:${TMP:4:2}:${TMP:6:2}:${TMP:8:2}:${TMP:10:2} TMP=${DATA:52} NAME=${TMP::-1} # And lookup the MAC from ARP cache IPADD="$(arp -an | grep "$MACADDR" | egrep -o '[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*')" # Likely, there was nothing there. Check, and use arping (needs root) if we need to case "$IPADD" in "") IPADD="$(arping "$MACADDR" -c 2 -i eth0 | egrep -o -m 1 '[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*' | head -1)" ;; esac
# Print out the information echo Model: $MODEL echo Serial: $SERIAL echo Name: $NAME echo MAC Address: $MACADDR
# Now check again to see if we have an IP address. If we do, we can get run-time information, eg battery level etc case "$IPADD" in "") echo "IP Address: Not known" ;; *) echo "IP Address: $IPADD" ./apc.sh ip=$IPADD status esac
fi;
if [ "$FUNCTION" = "SETIP" ]; then # Work out IP in Hex IPDEC="$(echo "$IPADDRESS" | sed 's/\./ /g')" TMP="$(printf '%02x ' $IPDEC ; echo)" IPHEX=${TMP::-1} RESULT="$(echo '12 50 00 a0 98 05 45 43 f5 f4 34 f6 '"$IPHEX" | xxd -r -p | socat - UDP4-DATAGRAM:255.255.255.255:9950,so-broadcast | tr -dc '[:print:]\n')" case "$RESULT" in "") echo "UPS did not respond.";; *) echo "UPS acknowledged command." esac fi;
if [ "$FUNCTION" = "SETNAME" ]; then TMP="$(echo "$NAME" | xxd -p )" NAMEHEX=${TMP::-2} RESULT="$(echo '12 50 00 a0 10 08 45 43 f5 '"$NAMEHEX"' 00' | xxd -r -p | socat - UDP4-DATAGRAM:255.255.255.255:9950,so-broadcast | tr -dc '[:print:]\n')" case "$RESULT" in "") echo "UPS did not respond.";; *) echo "UPS acknowledged command." esac fi;
# End of script.