Sophie

Sophie

distrib > Mandriva > 9.1 > ppc > by-pkgid > 37e222326095a93978d54b1564dd9954 > files > 164

apcupsd-3.10.5-1mdk.ppc.rpm

               The Virtual Uninterruptible Power Source
                   (C) 2000 Riccardo Facchetti

a. Revisions

Fri Jan 28 23:12:53 CET 2000 - Added 2.1.3.1 send_dumb_command().
                               Added chapter 5, considerations on dumb ups.
Fri Jan 21 23:54:04 CET 2000 - Initial writing.


b. Introduction

This document is published for free usage.
If you cite this document in bibliography or for reference I ask you to
recognize the authorship of the original. You may distribuite freely this
document as long as the original title, author name and copyright is kept
intact. You may use portions of this document as long as you recognize
clearly into your document the source of your quote. You may use this
document as a basis for the implementation of a generic ups monitoring
program as long as you recognize the ideas herein contained, citing the name
of the author and the title and revision of this document.

This document is intended to be a clarification for writing real code in
apcupsd. The use of this document for apcupsd, and for apcupsd only, is
totally free and don't need any aknowledgment.


c. Glossary

VUPS:   the Virtual UPS driver
UPS:    the ups driver
ups:    the ups hardware
dumb:   a simple electrical communication protocol
smart:  a well defined character-based communication protocol with which
        the program send a command and get a response from ups.
server: a computer with an attached and monitored UPS which is servicing
        the UPS over the network.
client: a computer attached to the same power line backed by the server's UPS
        but not to the ups serial line. To monitor the ups status it has to
        connect to the server and ask it for ups status.


1 What is VUPS

The VUPS layer is a mid-layer between apcupsd operations and real hardware.
To visualize the relations between apcupsd, VUPS and UPS here a scheme:

                       ------------------------
                       |                   PC |
                       |     ------------     |
                       |     | apc upsd |     |
                       |     ------------     |
                       |     | VUPS API |     |
                       |    --------------    |
                       |    | VUPS layer |    |
                       |    --------------    |
                       |     | UPS  API |     |
                       |    --------------    |
                       |    | UPS driver |    |
                       |    --------------    |
                       |    | Serial drv |    |
                       |    --------------    |
                       |                      |
                       ------------------------
                                  |
                                  | Serial cable
                                  |
                               -------
                               | UPS |
                               -------
                                  |
                                  | Power cord
                                  |
                              Main power


2 The UPS driver

As you can see, the main power is monitored by UPS that is attached to the PC
with a serial cable. The serial driver will manage I/O over the serial line.
On top of it there will be, in user space, the UPS driver.
The UPS driver will know anything of the UPS it is driving.
It will have knowledge of:

a) If the UPS is dumb or smart
b) In case the UPS is dumb will know which electrical signals on serial ports
   do or mean what.
c) In case the UPS is smart will know all the smart commands to
   query/configure/manage the UPS.

The UPS driver will translate b) and c) information using the UPS API that
will be mainly a "translation API" that will allow UPS to pass information on
up-layers.

The advantage of this scheme is that this way we will be able to drive any
kind of UPS, regardless of which protocol, smart or dumb, is using and which
signals or which serial protocol is used to communicate UPS events and status
or to reprogram UPS internal eprom.


2.1

Every UPS driver will have some lowlevel functions, described below.


2.1.1 int open(char *path)

This function will be needed to open the UPS line.

INPUT:
    char *path

    path can be one of these three:

    a) A path that point to a serial device
    b) A path that point to an USB device
    c) A network IP address

    a) and b) are obvious. We will have a path that point to a device.

    c) is less obvious. Since apcupsd can work in network and since the UPS
    driver is a hardware device but is not needed to be local, we may want to
    open an UPS device even from remote. The syntax of path for this case is
    very different. It will contain an hostname, or IP address, and,
    optionally, the port to which connect separated by hostname with a colon:

    "212.131.136.20"
    "212.131.136.20:3254"
    "master.oasi.gpa.it"
    "master.oasi.gpa.it:7832"

    On the other end of connection we will find the UPS driver of apcupsd.
    The two UPS drivers, the remote attached directly to the real UPS and the
    local one attached to the remote, will communicate through TCP/IP to
    exchange UPS information/commands.

OUTPUT:
    
    A file descriptor of the opened device, or the network connection.
    In case of error -1 will be returned and errno will be set.

As you can see until now I have not made particular hypotesis about the
internals of open(). This is because what we only need is that it return a
file descriptor over which communicate with the real hardware, being it
local or remote.


2.1.2 int close(int fd)

This function will close the communication between computer and UPS.

INPUT:

    fd is the file descriptor to be closed.

OUTPUT:

    zero on success, -1 on failure and errno will be set.

As you can notice, even for close() I have not made any particular
hypotesis of its inner workings. We are not interested in what is doing to
close the file descriptor. Only it need to stop gracefully the
communications between the UPS driver and the ups hardware.


2.1.3 int get_dumb_status(int fd)

This function gather the status of a dumb ups. It will return an integer
containing the read status of the ups.

INPUT:

    fd is the file descriptor of the dumb UPS to poll.

OUTPUT:

    The integer returned will contain any information that can be gathered
    from the UPS in dumb mode or -1 on error and errno set.

The returned integer will have to contain space for any possible meaning
of dumb signals transmitted electrically over the serial cable. This means
that will be a bitmap with or-ed bit values.

E.g.

#define DUMB_UPS_OK                 0x00
#define DUMB_UPS_ON_BATTERY         0x01
#define DUMB_UPS_BATTERY_FAILING    0x02

so that the returned integer may have the value:

(DUMB_UPS_ON_BATTERY|DUMB_UPS_BATTERY_FAILING)

Since this function is already very generic (of course being tailored to the
particular ups is driving), it is not needed to be wrapped with a translator
for upper layers: it can be called from upper layers.

2.1.3.1 int send_dumb_command(int fd, int command)

The dumb upses accept from the computer simple commands as status changes on
serial lines. The only command I know of dumb upses is the "shutdown ups"
command.

INPUT:
    fd is the file descriptor of the smart UPS.

    command is an integer that contain a command to be sent to the ups.

OUTPUT:
    an integer that will be zero on success or -1 on error and errno will be
    set.

Note that I suppose we don't get acks from upses when we send commands.


2.1.4 static int write_command(int fd, char *command)

This function is intended to be used with smart upses that communicate over
the serial line with a well defined protocol.

INPUT:
    fd is the file descriptor of the smart UPS.

    command is a string that contain a command to be sent to the ups.

OUTPUT:
    an integer that will be zero on success or -1 on error and errno will be
    set.

The command can be NULL and this will allow us to poll for ups status even if
we don't want to send it a particular command if it is not needed (see APC
upses behaviour on status change).

Note that I have not made any hypotesis on how the function will work
internally because it will be dependent on the ups firmware. In fact the
string sent to the ups for example will be ended with a CR, a CR+LF or
anything else that will be dependent on the particular ups.

Note that write_command is a function that is not called by upper layer
functions. It is internal to UPS.


2.1.5 static struct ups_string *read_command(int fd)

This function is intended to be used with smart upses that communicate over
the serial line with a well defined protocol.

INPUT:
    fd is the file descriptor of the smart UPS layer.

OUTPUT:
    A structure containing the string returned by the ups after a
    write_command() is sent or some alert that has occurred since the last
    read_string operation. NULL in case of error and errno will be set.

Note that ups_string is a static structure internal to read_command()

The ups_string will be:

struct ups_string {
    char *response;
#define STATUS_NONE            0
#define STATUS_ONBATTERY    1
#define STATUS_POWERFAIL    2
#define STATUS_POWERRETURN    3
#define any needed status
    int status;
};

The response member can be NULL when the ups have nothing to say us.
The status member can be one of the defined statuses.

The function calling read_command will be in charge to examine both response
and status members and to translate them to communicate information to the
upper layers. This means that read_string() function will not be used by any
upper layer function: it is internal to UPS layer.


2.1.6 char *talk_to_ups(int fd, int command)

This command is the interface between write/read_command() and the upper
layers. It is intended to send the ups a command, get the response, translate
it to a string recognizable by upper layers and return this string.

INPUT:
    fd is the file descriptor of the smart UPS.

    command is an integer that contain a command to be sent to the ups.
    This integer is a generic UPS command that this function will translate
    into a specific ups command string to pass to write_command()

OUTPUT:
    A char pointer that contain the translated answer of the ups. This string
    will be recognizable by the UPS layer.
    To account for response and status [see 2.1.5.] the returned string will
    be structured. This structure will be something like:

    "string containing translated response:string containing translated status"

    In this way no information will be lost passing to the upper layers.

Note that this function will be the main interface between VUPS and UPS.

2.1.7 struct UPS

This data structure will be responsible of exporting to the upper layers all
the functions implemented for the particular UPS driver.

struct UPS {
    int (*open)(char *path);
    int (*close)(int fd);
    int *(*get_dumb_status)(int fd);
    char *(*talk_to_ups)(int fd, int command);
};

Note that more than one UPS can be istantiated and opened and monitored by the
upper layers. Every UPS driver can be loaded, more than one time. This way we
open at installation with UPS monitoring fully centralized on a single
monitoring computer. For now we have still not identified the single UPS
because we are at a so low level that it makes no sense in being able to
distinguish between an UPS and another. This will be work for the upper
VUPS layer.

Note that for dumb upses, the talk_to_ups member will be NULL while for smart
upses the get_dumb_status will be NULL. This will tell the upper layers at
which kind of ups is referring the UPS without need to tell explicitly: it
will be done automatically at initialization.


3 The VUPS driver

Ths is the intermediate driver that will act as bridge between the UPS and the
apcupsd and its services.
This driver will interface to the UPS and to the apcupsd.

3.1 VUPS <-> UPS interface

Every UPS will be interfaced with the VUPS by the same VUPS that will register
any UPS that need to be loaded, at run time.

3.1.1 UPS is ready to be registered at run time

The UPS structure will be part of an UPS database that will be built at
compilation time:

typedef struct UPSdb {
    struct UPS *lowlevel;
    char *name;
} UPSdb;

UPSdb[] = {
    { &smartupsvs, "smartupsvs" },
    { &smartups, "smartups" },
    { &backups, "backups" }
    { NULL, NULL }
};

In this way apcupsd can parse the configuration file that will contain all the
UPSes that need to be monitored and find in the UPS database the right
`struct UPS' only knowing the name of the UPS driver.

for (i=0;UPSdb[i] != NULL;i++)
    if (!strcmp(cfg->upsname, UPSdb[i].name)
        attach_ups(cfg, UPSdb[i].lowlevel);

Note that with this scheme of run time registration we mantain opened the very
interesting idea to compile the UPS drivers as shared libraries and dlopen()
them at runtime, load and attach. This way we will not need to make a single
monster-executable but instead we can link apcupsd+VUPS and at run time link
only the UPSes drivers that we really need.

3.1.2 int attach_ups(UPScfg *cfg, struct UPS *lowlevel)

This function is meant to be used to attach to the VUPS all the UPS drivers.
The VUPS will have slots to contain any UPS that need to be attached.
A slot for every UPS.
There will be a linked list or a dynamic array of UPS structure pointers that
will be built by attach_ups().

INPUT:
    cfg is a pointer to an ups configuration. This is a full ups configuration
    in the config file. This configuration will be closed in brackets into the
    config file to allow multi-upses to be configured.

    E.g. a simplified configuration file could be:

    # Simple cfg file for multi-upses
    #
    UPS {
        name = smartups # name of the driver
        nick = ups1 # nick of the ups for network operations and logging
        device = /dev/ttyS0 # device
        poweringlocalhost = true # is this computer powered by this ups ?
        #
        # The latter is very important because will decide which ups
        # that powerfails and go timeout, will force a shutdown of the local
        # computer
        }

    UPS {
        name = backups
        nick = ups2
        device = /dev/ttuS1
        poweringlocalhost = false
        }
    UPS {
        name = smartupsvs
        nick = ups3
        device = /dev/ttyS2
        poweringlocalhost = false
        }

    lowlevel is the UPS driver corresponding to the configured ups.
    In our example we will attach the smartups, smartupsvs and backups
    drivers to the linked list of UPS structures of the upses to monitor.

OUTPUT:
    zero on success, -1 on error and errno will be set.

3.1.3 int add_ups(struct VUPS *newvups)

This will be for simplicity a dynamic array.
This function will create new VUPSes structures needed to interface UPSes to
apcupsd.

INPUT:
    struct VUPS to be added to the array of VUPSes

OUTOUT:
    zero on success, -1 on error and errno will be set

After all the UPSes are attached, the VUPS array will contain enough
information to monitor all the UPSes and tell apcupsd what is going on
with every UPS.


3.1.4 The struct VUPS

This structure will contain any information relevant to talk with both
UPS and apcupsd.

struct VUPS {
    struct UPScfg *cfg;    /* apcupsd configuration */
    struct UPS *lowlevel;  /* UPS lowlevel functions */
};

3.1.5 apcupsd <-> VUPS interface

The apcupsd will use VUPS->lowlevel->[function](...) to talk with the
UPS driver layer. This way it only need to know all the VUPSes present on the
system and will talk to any UPS through the VUPS interface that will
be the same for every VUPS. Don't miss this point because it is the major
advantage of this scheme.


4 The apcupsd daemon

The apcupsd daemon will be a general pourpose monitoring daemon. It will
monitor anything that is configured into the configuration file and defined in
the lower level drivers.


4.1 The apcupsd structure

The apcupsd daemon will have the current scheme:

a) monitor all the UPSes through the VUPS
b) sys-log all the events recorded
c) do actions on events if the UPS failing is local to the computer. Note that
   here, referring to the UPS, I'm referring even to remote, served, upses
   because the apcupsd or VUPS don't know absolutely if a particular UPS is
   local to the computer or remote through a network connection: this is
   hidden in the UPS lowlevel layer.
d) serve local UPSes to the network for clients that are residing on the same
   power supply line of the local UPSes but not attached to the serial line.
   This means that apcupsd will serve the information gathered by VUPS on a
   particular UPS through the network. Will wait for clients. Clients will
   talk with the VUPS through the network and will get any info they need
   about the status of UPS they have attached.

4.1.1 Monitoring

apcupsd will do every I/O on the upses using the VUPS interface.
The VUPS will be a single well defined interface so that apcupsd will talk
transparently with any kind of ups, as long as there is an UPS lowlevel driver
that can drive that particular ups.
Once the information are passed from the lower level UPS through VUPS to the
apcupsd, they will be written into the shared memory structure.

4.1.2 Sys-logging
Every event occurred at lower levels will be logged as soon as possible, in
the monitoring process. This operation is not much time consuming and using
the syslog() interface is even non-blocking so there isn't any issue against
doing it in the monitoring loop.


4.1.3 Actions on events

The actions that apcupsd will need to do in response to UPS events are all
local to the computer. Telling the users something is happening, shutdown the
computer if batteries are low or timeout is reached are all actions done
locally. These actions are done fork-exec'ing shell scripts that can be
modified by system administrators. To avoid blocking operations due to wrong
shell script, this part of the daemon will be a dedicated thread. The
information to decide actions will be gathered (read-only) from the shared
memory structure.


4.1.4 Servicing local UPSes to network

Note that Network server will be, for the network clients, exactly like a real
UPS. This means that the net server will act as a bridge between the VUPS and
the remote network daemon. This also means that the remote has to choose which
UPS it want to monitor because more UPSes can be istantiated on a single
computer.

The server will listen/accept on network sockets, then it will fork to start
servicing. This means that all the informations to serve to remote clients
will be read from the shared memory area. The remote client can read
information to know the status of ups write commands to the ups to ask for
particular actions. To do this, the shared memory will be partitioned in two
parts:

a) local part that will contain information updated by the server that will
   be read-only for the client
b) remote part that will contain information (commands) sent by the client to
   the server and that will be read-only for the server.

In this way we can have a two way communication, through VUPS, even between
network server and client. This means that the client can ask the server to
initiate an ups self test. This explanation somewhat explain what for the
client will be transparent. The client will see an UPS and will talk to an UPS
even if this UPS will be a network client attached to a server. The server
will manage this connection as if there will be an additional apcupsd running
on the same UPS. With a good locking scheme this will be easy to do and very
powerful.
To help visualize better the scheme, here some ASCII to explain it:

      ------------------------             ------------------------
      | Network client  PC 1 |             | Network server  PC 2 |
      |     ------------     |             |     ------------     |
      |     | apc upsd |     |      -------+---->| apc upsd |     |
      |     ------------     |     N|T     |     ------------     |
      |     | VUPS API |     |     e|C     |     | VUPS API |     |
      |    --------------    |     t|P     |    --------------    |
      |    | VUPS layer |    |     w|/     |    | VUPS layer |    |
      |    --------------    |     o|I     |    --------------    |
      |     | UPS  API |     |     r|P     |     | UPS  API |     |
      |    --------------    |     k|      |    --------------    |
      |    | UPS driver |<---+-------      |    | UPS driver |    |
      |    --------------    |             |    --------------    |
      |-+                    |             |    | Serial drv |    |
      |8|                    |             |-+  --------------    |
      ------------------------             |8|                    |
       |                                   ------------------------
       |                                    |         |
       |                                    |         | Serial cable
       |                                    |         |
       |        Backed up line              |      -------
       |-------------------------------------------| UPS |
                                                   -------
                                                      |
                                                      | Power cord
                                                      |
                                                  Main power



4.1.4.1 The Network protocol

The network protocol will be the protocol that translate between VUPS statuses
to network UPS statuses. Since we are the designers, this protocol will be
developed with the VUPS and will be able to send the entire VUPS through the
network. On the client side, the network UPS will only have to get the remote
VUPS information and send it to the upper layers.

The protocol will be ASCII strings. There are a couple of reasons that led me
to decide to use ASCII strings.

4.1.4.2 Chosing the UPS to attach

The network server at startup will listen to a socket.
The network client will connect to the server socket.
This is not enough to make the client connected to an UPS because on the
server can exist more than one UPS.
The client will have to choose which one. The server will have to allow the
client to attach to the chosen UPS.

a) client attach to server
b) server fork/exec
c) client write on the network fd which UPS want to monitor
   E.g.

   client: "CONNECT ups1"
   server: "CONNECTED ups1"
   or
   server: "DENIED ups1"

d) client start monitoring this ups through the network UPS driver.

5 The dumb UPS

Since we can not know in advance the seral electric "protocol" of a dumb ups,
we can write a generic support that can be used for all the dumb upses.
A serial line have a finite number of wires so a dumb ups can accept only a
finite number of commands and send only a finite number of states.
This means that we can write a generic UPS dumb driver and then when attaching
the generic UPS dumb driver to the VUPS layer, we simply tell the UPS how to
deal with the serial wires. This way we can write only one driver that is in
principle able to drive any kind of dumb ups.