5 apr 2012
i bought an UPS with two ports: serial and usb. and because i did not know much about the UPS (AEG - PROTECT HOME VA 600) i started to look at the communication protocol. turns out there are lots of good tools for serial line interception but nearly none for the usb stuff. sadly the driver i wrote isn’t needed at all as an email to the nut-ML revealed that this UPS uses the Q1 protocol which is already supported pretty well using the blazer_usb and blazer_ser module.
anyway it was pretty interesting to hack on NUT using debian and later nixos. so here is a guide how to log/analyze serial traffic and how to write a simulator for either side.
and not to forget:** thanks to Arnaud Quette for his ups/nut support. there is also a brief nut setup introduction**, see [1].
set serial settings to:
enable serial port port number: com 1 irq 4 io port 0x3f8 port mode: host device port/file path: /tmp/interceptty
maybe correct the permissions to /tmp/interceptty
interceptty -s ‘ispeed 2400 ospeed 2400’ -l /dev/ttyS0 | tee mylog | interceptty-nicedump
on the linux host:
tail -F mylog | grep “<”**
start virtualbox vm with a windows xp** installed
note: ignore this virtualbox warning: “Ioctl failed for serial device ‘/tmp/interceptty’ (VERR_INVALID_PARAMETER). The device will not work properly.”. it works anyway, at least on my system (using ubuntu 10.10 with standard virtualbox).
on the server side open /dev/remserialVM
remserial -d -p 23000 -s “2400 raw” -l /dev/remserialVM /dev/ptmx
on the client side (same host), do:
remserial -d -r 127.0.0.1 -p 23000 -s “2400 raw” -l /dev/remserialPY /dev/ptmx
chmod 0777 /dev/remser*
change the virtualbox serial settings:
port mode: host device port/file path: /dev/remserialVM
then format a ‘message’ with a hexeditor also called “hexeditor”
start the vm
then send the formated message: cat message > /dev/remserialPY
if the message was received by the windows ups monitoring software (it will think that the message it received originated from the UPS and not that it was crafted manually)
note: instead of manually sending messages, i also used the script: ./simulate-ups.py which does that automatically.
note: simulate-ups-monitor.py can be used in an analog way but simply using the ups with a real serial port. i should mention btw, that i was using both a usb2serial adapter and an old computer which still contains one of those ancient serial ports.
#!/usr/bin/python
import serial
ser = serial.Serial('/dev/remserialPY', 2400)
line = ''
count=0
def process_command(cmd):
print " < incomming: " + cmd
if cmd == "Q1":
print "REQUEST FOR DATA FROM USV"
n = ("20").decode("hex")
d = ("0d").decode("hex")
a = ("28").decode("hex") + \
"000.0" + n + \
"000.0" + n + \
"000.5" + n + \
"005" + n + \
"00.0" + n + \
"00.6" + n + \
"25.0" + n + \
"00000001" + d
ser.write(a)
while True:
ch = ser.read(1)
if ch == "\x0d":
process_command(line)
line = ''
else:
line = line + ch
#!/usr/bin/python
import serial
import re
import time
import sys
ser = serial.Serial('/dev/ttyS0', 2400)
#, serial.EIGHTBITS, serial.PARITY_NONE, serial.STOPBITS_ONE, 0)
line = ''
count=0
status="unknown"
def write(cmd):
#print "sending " + cmd;
ser.write(cmd)
def print_status(status):
print "status is: Unknown|LostCom|Normal|ScheduledShutdown|60SecsShutdown|ActiveShutdown|CriticalPowerFail: " + status
def process_command(cmd):
valid = re.compile(r"\([0-9][0-9][0-9].[0-9] [0-9][0-9][0-9].[0-9] [0-9][0-9][0-9].[0-9] [0-9][0-9][0-9] [0-9][0-9].[0-9] [0-9][0-9].[0-9] [0-9][0-9].[0-9] [01][01][01][01][01][01][01][01]")
if valid.match(cmd):
#print status + " : VALID REPLY FROM USV -> " + cmd
#(239.5 239.5 235.6 000 49.9 13.6 25.0 00001001
netz_eingang=cmd.split(' ')[0].lstrip('(')
netz_unknown=cmd.split(' ')[1]
netz_ausgang=cmd.split(' ')[2]
percent=cmd.split(' ')[3]
hz=cmd.split(' ')[4]
bat_voltage=cmd.split(' ')[5]
temperature=cmd.split(' ')[6]
bits=cmd.split(' ')[7]
bit1=bits[0]
bit2=bits[1]
bit3=bits[2]
bit4=bits[3]
bit5=bits[4]
bit6=bits[5]
bit7=bits[6]
bit8=bits[7]
print status + " " + cmd
else:
print "invalid reply detected: " + cmd
sys.exit(1)
write( ("51310d").decode("hex"))
while True:
ch = ser.read(1)
if ch == "\x0d":
process_command(line)
time.sleep(1)
write( ("51310d").decode("hex"))
line = ''
else:
line = line + ch
# ./simulate-ups-monitor.py
VALID REPLY FROM USV -> (241.5 241.4 237.5 000 49.9 13.5 25.0 00001001
VALID REPLY FROM USV -> (241.5 241.4 237.5 000 49.9 13.5 25.0 00001001
VALID REPLY FROM USV -> (241.4 241.4 237.5 000 49.9 13.5 25.0 00001001
so would i buy a AEG Protect Home VA 600 again? currently there is no ‘time left’ estimation and therefore i shutdown the system either after 25 seconds or on LB (low battery) but after reloading the batteries the shutdown usually is triggered by the 25 seconds rule after a state change to OB (on battery). i think this is a decent setup and therefore i would probably buy that UPS again. but i don’t really have a clue about UPS devices so there might be much better ones in the same price range, maybe someone on the NUT/UPS ML can make a better recommendation.
what i really dislike is that this product ships with linux support BUT not with NUT support. i later realized that they created their own linux software. what a waste of time, i would rather love to get the specification and then use NUT instead - probably this is the case for nearly all the users seeing that this devices has linux support. but my request to get the specification was simply ignored, so i think there are better vendors out there.
another interesting aspect of nut is how complex the integration in the system is.