programming with serial ports in linux

5 apr 2012

what is this?

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].

sniff serial port data between UPS and PC

  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

  2. maybe correct the permissions to /tmp/interceptty

  3. interceptty -s ‘ispeed 2400 ospeed 2400’ -l /dev/ttyS0 | tee mylog | interceptty-nicedump

  4. on the linux host:

    tail -F mylog | grep “<”**

  5. 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).

using the virtual python UPS

  1. on the server side open /dev/remserialVM

    remserial -d -p 23000 -s “2400 raw” -l /dev/remserialVM /dev/ptmx

  2. on the client side (same host), do:

    remserial -d -r -p 23000 -s “2400 raw” -l /dev/remserialPY /dev/ptmx

  3. chmod 0777 /dev/remser*

  4. change the virtualbox serial settings:

    port mode: host device port/file path: /dev/remserialVM

  5. then format a ‘message’ with a hexeditor also called “hexeditor”

  6. start the vm

  7. then send the formated message: cat message > /dev/remserialPY

  8. 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: ./ which does that automatically.

note: 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.

import serial
ser = serial.Serial('/dev/remserialPY', 2400)
line = ''
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

while True:
        ch =
        if ch == "\x0d":
                line = ''
                line = line + ch

import serial
import re
import time
import sys

ser = serial.Serial('/dev/ttyS0', 2400)
#, serial.EIGHTBITS, serial.PARITY_NONE, serial.STOPBITS_ONE, 0)

line = ''

def write(cmd):
        #print "sending " + 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]
                print status + " " + cmd
                print "invalid reply detected: " + cmd

write( ("51310d").decode("hex"))

while True:
        ch =
        if ch == "\x0d":
                write( ("51310d").decode("hex"))
                line = ''
                line = line + ch

simulate the UPS monitor

# ./
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.

article source