On the surface, Belkin seems Linux-friendly, because they provide a Linux version of their "Bulldog Plus" UPS management software. This software is not included on the CD Rom which ships with the UPS, but one can download it from Belkin's website.
However, unfortunately, the Bulldog Plus software is not as useful as it might seem. It has many bugs (see Bugs in Bulldog), and it cannot be configured to allow an unsupervised recovery (the user must press the front panel button to restart the UPS after a power failure).
Since the specifications of the protocol were not available, I decided to do some detective work and to decipher the protocol myself. The results are described in this document. The information reported here is necessarily incomplete, but it seems that the most important aspects of the protocol have been covered.
Unfortunately, there is one serious problem with the "Universal UPS" firmware: there is no command which causes the UPS to go into "soft shutdown" mode, which means, to shut off the load until AC power returns. This makes unsupervised recovery from power failures tricky and awkward (see 4. Soft Shutdown Workaround for a suggested solution).
On balance, the Belkin UPS works fine, but the lack of a "soft shutdown" command is annoying. If you are thinking about buying a UPS, I think currently APC is a better choice than Belkin at approximately the same price. Maybe Belkin will come up with an improved version of their firmware in the future.
Only the "Universal UPS" is described in this document.
To understand the issue, consider what should normally happen to an unsupervised computer (such as a server) during a power failure:
The problem with the Belkin UPS is that step 3 is not possible. One cannot signal the UPS to do a "soft shutdown". One can only signal it to do a "final shutdown", which means, the UPS stays off even if the power returns. This leaves the entire system in a state where a human needs to press the button on the UPS front panel to restart the system.
There is one way around this problem. Namely, the UPS will enter "soft shutdown" mode when its batteries run out. Thus, it is possible to handle a power failure as follows:
I have implemented the required functionality as part of the NUT belkinunv driver. The belkinunv driver can be used as a standalone program by calling it with the "-x wait" option. When called with this option, the driver does not fork into the background, but simply connects to the UPS and waits for AC power to be restored. The intention is that one puts commands such as the following as the last part of the computer's shutdown script:
# NEAR END OF SHUTDOWN SCRIPT: # if shutdown was caused by UPS, perform Belkin UPS workaround. if [ -f /etc/killpower ] ; then echo "Waiting for AC power, or for UPS batteries to run out..." /usr/bin/belkinunv -x wait /dev/ttyS1 # we get here if the power came back on. Reboot. echo "Power is back. Rebooting..." reboot fi |
In addition, when called with the "-x wait=level" option, the belkinunv driver waits for the battery charge to reach the specified level. This can be used in a startup script as follows. Put this before any disks are mounted read/write, and before any file system integrity checks, so that the system is in a safe state.
# NEAR BEGINNING OF STARTUP SCRIPT: # if we are recovering from a power failure, wait for the UPS to # charge to a comfortable level before writing anything to disk if [ -f /etc/killpower ] ; then echo "Waiting for UPS battery charge to reach 60%..." /usr/bin/belkinunv -x wait=60 -x nohang /dev/ttyS1 fi |
Here, the "-x nohang" option ensures that the computer will boot properly in case the UPS is no longer attached. There might be many reasons to remove the UPS during a power failure, for instance, you take your computer to a friend's house, or you attach it to a generator, or whatever. Giving the "-x nohang" option ensures that your computer is always bootable.
For a detailed description of the "-x wait" and related options, please see the belkinunv(8) man page.
In order to get the UPS to switch to "smart" mode, one first needs to set RTS and drop DTR for at least 0.25 seconds. (RTS and DTR refer to two specific pins in the 9-pin serial connector). This signals the UPS to switch to serial mode. Without this step, there will be no serial communication possible. In experimenting with my own UPS, I found that 0.25 seconds is the minimum time required for the UPS to react; however, the time required may differ for different UPS hardware. The Bulldog software waits 1 second, so this is probably a safe bet.
After this, flush any unread garbage bytes from the serial port. Thereafter, simply communicate with the port via usual "read" and "write" operations.
The following C procedure shows exactly how the serial port should be opened and prepared:
#include <fcntl.h> #include <sys/ioctl.h> #include <string.h> #include <errno.h> #include <termios.h> #include <unistd.h> /* Open and prepare a serial port for communication with a Belkin Universal UPS. DEVICE is the name of the serial port. It will be opened in blocking read/write mode, and the appropriate communications parameters will be set. The device will also be sent a special signal (clear DTR, set RTS) to cause the UPS to switch from "dumb" to "smart" mode, and any pending data (=garbage) will be discarded. After this call, the device is ready for reading and writing via read(2) and write(2). Return a valid file descriptor on success, or else -1 with errno set. */ int belkin_open_tty(char *device) { int fd; struct termios tios; struct flock flock; char buf[128]; const int tiocm_dtr = TIOCM_DTR; const int tiocm_rts = TIOCM_RTS; int r; /* open the device */ fd = open(device, O_RDWR | O_NONBLOCK); if (fd == -1) { return -1; } /* set communications parameters: 2400 baud, 8 bits, 1 stop bit, no parity, enable reading, hang up when done, ignore modem control lines. */ memset(&tios, 0, sizeof(tios)); tios.c_cflag = B2400 | CS8 | CREAD | HUPCL | CLOCAL; tios.c_cc[VMIN] = 1; tios.c_cc[VTIME] = 0; r = tcsetattr(fd, TCSANOW, &tios); if (r == -1) { close(fd); return -1; } /* signal the UPS to enter "smart" mode. This is done by setting RTS and dropping DTR for at least 0.25 seconds. RTS and DTR refer to two specific pins in the 9-pin serial connector. Note: this must be done for at least 0.25 seconds for the UPS to react. */ r = ioctl(fd, TIOCMBIC, &tiocm_dtr); if (r == -1) { close(fd); return -1; } r = ioctl(fd, TIOCMBIS, &tiocm_rts); if (r == -1) { close(fd); return -1; } /* flush both directions of serial port: throw away all data in transit */ r = tcflush(fd, TCIOFLUSH); if (r == -1) { close(fd); return -1; } /* lock the port */ memset(&flock, 0, sizeof(flock)); flock.l_type = F_RDLCK; r = fcntl(fd, F_SETLK, &flock); if (r == -1) { close(fd); return -1; } /* sleep at least 0.25 seconds for the UPS to wake up. Belkin's own software sleeps 1 second, so that's what we do, too. */ usleep(1000000); /* flush incoming data again, and read any remaining garbage bytes. There should not be any. */ r = tcflush(fd, TCIFLUSH); if (r == -1) { close(fd); return -1; } r = read(fd, buf, 127); if (r == -1 && errno != EAGAIN) { close(fd); return -1; } /* finally, switch to blocking i/o, so that future read/write calls will read or write at least one byte */ r = fcntl(fd, F_SETFL, 0); /* clear O_NONBLOCK */ if (r == -1 && errno != EAGAIN) { close(fd); return -1; } return fd; } |
The format of all messages is:
Header | Type | Length | Register ID | Data | Checksum |
1 byte | 1 byte | 1 byte | 1 byte | variable | 1 byte |
The message fields are explained as follows:
Header: | The literal byte 0x7e, or ASCII '~' |
Type: |
There are five possible message types:
|
Length: | The number of bytes in the Data field, plus 1. Equivalently, the length of the entire message, minus 4. |
Register ID: | The number of the register to be read or written. |
Data: |
The argument of the message. The precise meaning depends on the type
of message.
|
Checksum: | The checksum for this message. It equals the sum of all previous bytes of the message (including the initial 7e), modulo 0x100. |
Format of responses:
Each command (computer to UPS) is followed by a single response (UPS to computer). A read command (type=3) is normally followed by a read response (type=5), or sometimes by an error response (type=1). A write command (type=4) is normally followed by a write response (type=2), or sometimes by an error response (type=1).
The Register ID field of the response is identical to the Register ID field of the command being responded to. Moreover, the Data field of the response is identical to the Data field of the command being responded to, in case of an error response or a write response. In case of a read response, the Data field is of course the value being read.
An error response is only sent if the corresponding command was well-formed (including a correct checksum), but cannot be obeyed for some reason (e.g., the corresponding register is not implemented). Ill-formed commands are silently ignored.
Reading a 1-byte register: | |||
Command: | 7e 03 02 01 00 84 | read register 01, voltage rating | |
Response: | 7e 05 02 01 78 fe | register 01 is 0x78 = 120 = 120V | |
Reading a 2-byte register: | |||
Command: | 7e 03 02 20 00 a3 | read register 20, battery voltage | |
Response: | 7e 05 03 20 0e 01 b5 | register 20 is 0x010e = 269 = 26.9V | |
Reading a string register: | |||
Command: | 7e 03 02 0d 00 90 | read register 0d, UPS model | |
Response: | 30 2d 55 4e 56 20 20 20 7b |
register 0d is "F6C800-UNV " | |
Reading a non-existent register: | |||
Command: | 7e 03 02 1a 00 9d | read register 1a, temperature | |
Response: | 7e 01 02 1a 00 9b | error: cannot read register 1a | |
Writing to a register: | |||
Command: | 7e 04 03 0c 02 00 93 | write 0x02 to register 0c, voltage sensitivity | |
Response: | 7e 02 03 0c 02 00 91 | written successfully | |
Writing to a non-existent register: | |||
Command: | 7e 04 03 00 00 00 85 | write 0x00 to register 00 | |
Response: | 7e 01 03 00 00 00 82 | error: cannot write to register 00 |
Each register is identified by a unique number, the register ID. We always write register ID's as hexadecimal values.
Registers can hold different types of data. Some registers hold a 1-byte integer, others hold a 2-byte integer, and others hold a variable length string.
Some registers are read only once (during startup) by the Bulldog software, and thereafter ignored. These registers are marked "startup" in the list below. All other registers are read periodically.
We now describe the individual registers:
Reg.ID | Name | Data | Flags | Typical Value |
Description |
---|---|---|---|---|---|
00: | unknown / does not exist | n/a | n/a | n/a | This register does not really exist. It is written by the linux driver when the user requests to set the voltage sensitivity. However, this is a bug and the UPS responds with an error message. The Windows driver correctly writes register 0c instead. |
01: | voltage rating | 1 byte | RO HC ST | 120 | The nominal voltage in V. In North America, this is 120. |
02: | frequency rating | 1 byte | RO HC | 60 | The nominal AC frequency in Hz. In North America, this is 60. |
03: | power rating | RO HC ST | 800 | The nominal power rating in VA. For a 800VA UPS, this is 800. | |
04: | battery voltage rating | 1 byte | RO HC ST | 24 | The nomial battery voltage in V. For my system, this is 24. |
05: | unknown | 1 byte | RO ST | 100 | I don't know the function of this register. On my system, it takes value 0x64 = 100. |
06: | low transfer voltage | 2 bytes | RW | 90 |
I am not entirely sure about the meaning of registers 06-0b, but I
think that they are voltage transfer points. 06 and 09 are
read/write, the others are read-only.
The only way to test whether these "transfer points" actually work would be to vary the UPS input voltage and observe what happens. I don't have equipment to do this. Ditto for register 0c, voltage sensitivity. Update 2004/06/14: Thomas Bliesener in Mexico has confirmed that registers 06 and 09 are indeed voltage transfer points. He writes: "It's easy to verify: connect the ups to a mexican power supply system. I wondered why my server was off every morning with the new Belkin UPS and then I discovered that the voltage rose at night from nominal 127 V to 136 V. With the buggy bulldog software I couldn't change the high voltage transfer, with your console.c it was simple." |
07: | low transfer voltage upper bound | 2 bytes | RO HC ST | 95 | |
08: | low transfer voltage lower bound | 2 bytes | RO HC ST | 85 | |
09: | high transfer voltage | 2 bytes | RW | 136 | |
0a: | high transfer voltage upper bound | 2 bytes | RO HC ST | 141 | |
0b: | high transfer voltage lower bound | 2 bytes | RO HC ST | 131 | |
0c: | voltage sensitivity | 1 byte | RW | 0 | The voltage sensitivity can be read and written. Admissible values are 0=normal, 1=medium, 2=low. Presumably this influences how tolerant the UPS is to deviations from the "nominal" voltage; however, I have not found this point elaborated in Belkin's documentation. I am not sure how this interacts with registers 06-0b. |
0d: | UPS model | string | RO HC ST | The UPS model name as a sequence of ASCII characters, not null-terminated. For my system, registers 0d and 0e are identical, and both contain the string "F6C800-UNV " (with three space characters at the end of the string). | |
0e: | UPS model | string | RO HC ST | ||
0f: | firmware/ups type | 1 byte | RO HC ST | 0x41 |
The higher 4 bits determine the firmware version.
The lower 4 bits determine the UPS type: 0=online, 1=offline, 2=line-interactive. In the Belkin Monitor, this is used to determine the type of "wiring diagram" displayed (and does not affect anything else, as far as I can tell). Online:
Offline:
Line-interactive: Example: a value of 0x41 means firmware version = 4, UPS type = offline. |
10: | battery test status | 1 byte | RW | 0 |
When read, this register takes the following values:
|
11: | audible alarm status | 1 byte | RW | 2 |
A flag which determines whether the audible alarm of the UPS
system is enabled.
Note: the alarm can only be disabled when it is already ringing. In other words, the alarm is enabled each time an alarm-worthy event occurs, but can then be disabled during the event. Also note: during a critical event (e.g., low battery), the alarm rings faster and cannot be disabled. |
12: | unknown | 1 byte | RW | 0 | I don't know the function of registers 12-14. On my system, they all take value 0. Setting these registers has no apparent effect; subsequents reads will still return 0. |
13: | unknown | 1 byte | RW | 0 | |
14: | unknown | 1 byte | RW | 0 | |
15: | shutdown timer | 2 bytes | RW | 0 |
This register holds the time, in seconds, until the UPS shuts
down its load, or 0 if no shutdown is scheduled. It can be read and
written.
Note: a scheduled shutdown cannot be stopped. Setting a running shutdown timer to 0 causes it to continue to count down from 0xffff = 65535. This is idiotic in my opinion, but it is what my Belkin UPS does. Note: when this counter reaches 0, the UPS either performs a "final shutdown" or a "timed shutdown", never a "soft shutdown" as far as I can tell (see 4. Soft Shutdown Workaround above for more information). Which kind of shutdown is performed depends on the state of the restart timer, see register 16 below. Warning: the Bulldog software panics and wants to shut down the operting system immediately if it sees that register 15 is non-zero. |
16: | restart timer | 2 bytes | RW, ST | 0 |
This register holds the time, in minutes (not seconds!), until the
UPS will restart its load, or 0 if the timer is not set.
Once this timer is set, it will be decremented once a minute; however, one cannot predict when the first decrement will happen (it happens between 0 and 60 seconds after the counter was set). There is no way to stop a running timer: setting it to 0 will cause it to loop back to 0xfff = 65535. When the shutdown timer (register 15) reaches 0, then the value of the restart timer (register 16) at that particular time determines what kind of shutdown the UPS performs. If the restart timer is 0, a "final shutdown" will be performed, otherwise a "timed shutdown". After a "final shutdown", flag 0x0020 in register 22 will be set, and the UPS will remain off permanently until the front panel button is pressed. It do not currently know a way of re-starting it through the serial port. Also, the system remains off irrespectively of what happens to the AC power. In the "final shutdown" state, all timers (register 15 and 16) are stopped and no longer work; thus there is no way of ending a final shutdown in software. If AC power is lost during a final shutdown, the UPS loses communication on its serial port. During a "timed shutdown", flag 0x0020 in register 22 will not be set. Register 23 will be 0x10 if AC power is on, or else 0x00. The system load will be turned back on as soon as the restart timer reaches 0. If any non-zero value is written to the restart timer during a timed shutdown, the system load will be immediately turned on (and the restart timer is reset to 0). Note: to initiate a timed shutdown, write 2 to register 16, then write 1 to register 15. The reason we write "2" and not "1" to register 16 is that there is no guarantee it is not decremented between the two writes. |
17: | unknown | 1 byte | RW | 0 | I don't know the function of this register. On my system, it takes value 0. I found that if this register is non-zero, then the Bulldog software initiates an immediate shutdown. But it does not specify the reason. Setting this register has no apparent effect; subsequents reads will still return 0. |
18: | AC input voltage | 2 bytes | RO | 1176 | The current AC input voltage in 0.1V, or 0 on loss of utility power. Note that this value is non-zero as long as AC power is present, even during a battery test. Example: 1176 = 117.6V |
19: | AC input frequency | 2 bytes | RO | 599 | The current AC input frequency in 0.1Hz, or 0 on loss of utility power. Note that this value is non-zero as long as AC power is present, even during a battery test. Example: 599 = 59.9Hz. |
1a: | temperature | 1 byte | RO | 23 | The current UPS temperature, in degrees C. My UPS does not actually implement this register, but the Bulldog software recognizes it. |
1b: | AC output voltage | 2 bytes | RO | 1168 | The current AC output voltage in 0.1V, both during on-line and battery operation. Example: 1168 = 116.8V. |
1c: | AC output frequency | 2 bytes | RO | 598 | The current AC output frequency in 0.1Hz, both during on-line and battery operation. Example: 598 = 59.8Hz. |
1d: | unknown | 1 byte | RO | 250 | I don't know the function of this register. On my system, it takes value 250. |
1e: | loading level | 1 byte | RO | 35 | The current loading level in percent. |
1f: | battery status | 1 byte | RO ST | 0x10 | Apparently identical to register 23. |
20: | battery voltage | 2 bytes | RO | 248 | The current battery voltage in 0.1V. Example: 248 = 24.8V. |
21: | battery level | 1 byte | RO | 86 | The current battery level in percent. |
22: | UPS status | 2 bytes | RO | 0x8000 |
This register holds the following flags. Since some of these
conditions rarely occur, I am not sure whether my UPS implements
all of them; anyway, the Bulldog Monitor recognizes them.
The Buzzer status flag is somewhat strange. On my UPS, it seems to be set precisely if (1) the UPS is operating on Battery power and the alarm is physically ringing, including during a test, or (2) the UPS is operating on AC power. The Bulldog software uses this flag as the basis for the "Buzzer Alarm" status flag (in the "Status" display), with the annoying effect that this flag is almost always on, even when the buzzer is not ringing. Maybe this is a bug in my UPS's firmware. In any case, it seems that the buzzer is actually ringing if and only if flag 0x8000 in register 22 and flag 0x20 in register 23 are both set. Also note that my UPS sets 0x0005 on AC Failure; however, the 0x0004 bit is apparently ignored by the Bulldog software. Finally, if the 0x0008 flag is set during an AC Failure, then the "UPS Health" status line in the "Main" display will turn orange. If 0x0008 is not set, this is not necessarily the case (unless, strangely, register 1f was 0x00 or 0x20 during startup). It is not clear to what extent this behavior means anything, and to what extent it just reveals idiosyncracies in the Bulldog software. |
23: | battery status | 1 byte | RO | 0x10 |
This register holds the following flags.
Notes: here is what happens if AC power gets disconnected and we continue the load. First, the UPS will switch to battery power (and the buzzer will beep every 10 seconds unless disabled). The UPS status becomes 0x8005 (or 0x0005 if the buzzer is disabled). The battery status becomes 0x20. The battery voltage and battery level will decrease continually. When the battery level falls below 15, the "battery low" flag will come on, thus the battery status become 0x24. At the same time the buzzer will start ringing fast and can no longer be disabled. The UPS status is unchanged at 0x8005. The battery voltage and level will continue to drop, and the UPS will continue to supply power, even when the battery level reaches 0. Finally, after the battery level has been 0 for a while, the UPS turns its load off and does a "soft shutdown". At this point, the battery status becomes 0x40 (battery depleted) and the UPS status becomes 0xa001 (AC failure and awaiting power). If one now turns the AC power back on, the UPS will power up and enter UPS status 0x8000 and battery status 0x10. This is the only scenario in which I have observed a "soft shutdown", as indicated by UPS status flag 0x2000. I still don't know how to bring about a soft shutdown manually. Therefore, the workaround under 4. Soft Shutdown Workaround above is necessary. Note that, based just on registers 22 and 23, it is not in general possible to determine whether the UPS is "online", i.e., whether the load is currently powered by AC power. Indeed, if the load has been turned off manually (e.g. due to a timed shutdown), the UPS status may well be 0x8000, and the battery status may be 0x10, just as during normal operation. We therefore use the following method for determining whether the system is "online", "on battery", or "off load": if battery status flag 0x20 is set, the system of "on battery", else if the output voltage is non-zero, the system is "online", else the system is "off load". |
24: | unknown | 1 byte | RW | 0 | I don't know the function of registers 24-27. On my system, they all take value 0. Setting these registers has no apparent effect; subsequents reads will still return 0. The Bulldog driver does not attempt to read these registers. |
25: | unknown | 1 byte | RW | 0 | |
26: | unknown | 1 byte | RW | 0 | |
27: | unknown | 1 byte | RW | 0 | |
3f: | time remaining | 1 byte | RO | 0 | The estimated backup time until the battery runs out, in minutes. My UPS does not actually implement this register, but the Bulldog software recognizes it. |
In no event unless required by applicable law or agreed to in writing will any of the authors of this document, or any other party who may modify and/or redistribute this document, be liable to you for damages, including any general, special, incidental or consequential damages arising out of the use or inability to use the information (including but not limited to loss of data or data being rendered inaccurate or losses sustained by you or third parties), even if such author or other party has been advised of the possibility of such damages.