B.2. Implementation notes

General specification

The daisychain support uses the device collection to extend the historical NUT scope (1 driver — 1 device), and provides data from the additional devices accessible through a single management interface.

A new variable was introduced to provide the number of devices exposed: the device.count, which:

  • defaults to 1
  • if higher than 1, enables daisychain support and access to data of each individual device through device.X.{...}

To ensure backward compatibility in NUT, the data of the various devices are exposed the following way:

  • device.0 is a special case, for the whole set of devices (the whole daisychain). It is equivalent to device (without .X index) and root collections. The idea is to be able to get visibility and control over the whole daisychain from a single point.
  • daisy-chained devices are available from device.1 (master) to device.N (slaves).

That way, client applications that are unaware of the daisychain support, will only see the whole daisychain, as it would normally seem, and not nothing at all.

Moreover, this solution is generic, and not specific to the ePDU use case currently considered. It thus support both the current NUT scope, along with other use cases (parallel / serial UPS setups), and potential evolution and technology change (hybrid chain with UPS and PDU for example).

Devices status handling

To be clarified…

Devices alarms handling

Devices (master and slaves) alarms are published in device.X.ups.alarm, which may evolve into device.X.alarm. If any of the devices has an alarm, the main ups.status will publish an ALARM flag. This flag is be cleared once all devices have no alarms anymore.

Note

ups.alarm behavior is not yet defined (all devices alarms vs. list of device(s) that have alarms vs. nothing?)

Example

Here is an example excerpt of three PDUs, connected in daisychain mode, with one master and two slaves:

device.count: 3
device.mfr: EATON
device.model: EATON daisychain PDU
device.1.mfr: EATON
device.1.model: EPDU MI 38U-A IN: L6-30P 24A 1P OUT: 36XC13:6XC19
device.2.mfr: EATON
device.2.model: EPDU MI 38U-A IN: L6-30P 24A 1P OUT: 36XC13:6XC19
device.3.mfr: EATON
device.3.model: EPDU MI 38U-A IN: L6-30P 24A 1P OUT: 36XC13:6XC19
...
device.3.ups.alarm: high current critical!
device.3.ups.status: ALARM
...
input.voltage: ??? (proposal: range or list or average?)
device.1.input.voltage: 237.75
device.2.input.voltage: 237.75
device.3.input.voltage: 237.75
...
outlet.1.status: ?? (proposal: "on, off, off)
device.1.outlet.1.status: on
device.2.outlet.1.status: off
device.3.outlet.1.status: off
...
ups.status: ALARM

Information for developers

Note

these details are dedicated to the snmp-ups driver!

In order to enable daisychain support for a range of devices, developers have to do two things:

  • Add a device.count entry in a mapping file (see *-mib.c)
  • Modify mapping entries to include a format string for the daisychain index

Optionally, if there is support for outlets and / or outlet-groups, there is already a template formatting string. So you have to tag such templates with multiple definitions, to point if the daisychain index is the first or second formatting string.

Base support

In order to enable daisychain support on a mapping structure, the following steps have to be done:

  • Add a "device.count" entry in the mapping file: snmp-ups will determine if the daisychain support has to be enabled (if more than 1 device). To achieve this, use one of the following type of declarations:

    a) point at an OID which provides the number of devices:

    { "device.count", 0, 1, ".1.3.6.1.4.1.13742.6.3.1.0", "1",
            SU_FLAG_STATIC, NULL },

    b) point at a template OID to guesstimate the number of devices, by walking through this template, until it fails:

    { "device.count", 0, 1, ".1.3.6.1.4.1.534.6.6.7.1.2.1.2.%i", "1",
            SU_FLAG_STATIC, NULL, NULL },
  • Modify all entries so that OIDs include the formatting string for the daisychain index. For example, if you have the following entry:

    { "device.model", ST_FLAG_STRING, SU_INFOSIZE,
            ".1.3.6.1.4.1.534.6.6.7.1.2.1.2.0", ... },

    And if the last "0" of the the 4th field represents the index of the device in the daisychain, then you would have to adapt it the following way:

    { "device.model", ST_FLAG_STRING, SU_INFOSIZE,
            ".1.3.6.1.4.1.534.6.6.7.1.2.1.2.%i", ... },
Templates with multiple definitions

If there already exist templates in the mapping structure, such as for single outlets and outlet-groups, you also need to specify the position of the daisychain device index in the OID strings for all entries in the mapping table, to indicate where the daisychain insertion point is exactly.

For example, using the following entry:

{ "outlet.%i.current", 0, 0.001, ".1.3.6.1.4.1.534.6.6.7.6.4.1.3.0.%i",
        NULL, SU_OUTLET, NULL, NULL },

You would have to translate it to:

{ "outlet.%i.current", 0, 0.001, ".1.3.6.1.4.1.534.6.6.7.6.4.1.3.%i.%i",
        NULL, SU_OUTLET | SU_TYPE_DAISY_1, NULL, NULL },

SU_TYPE_DAISY_1 flag indicates that the daisychain index is the first %i specifier in the OID template string. If it is the second one, use SU_TYPE_DAISY_2.

Devices alarms handling

Two functions are available to handle alarms on daisychain devices in your driver:

  • device_alarm_init(): clear the current alarm buffer
  • device_alarm_commit(const int device_number): commit the current alarm buffer to "device.<device_number>.ups.alarm", and increase the count of alarms. If the current alarms buffer is empty, the count of alarm is decreased, and the variable "device.<device_number>.ups.alarm" is removed from publication. Once the alarm count reaches "0", the main (device.0) ups.status will also remove the "ALARM" flag.

Note

When implementing a new driver, the following functions have to be called:

  • alarm_init() at the beginning of the main update loop, for the whole daisychain. This will set the alarm count to "0", and reinitialize all alarms,
  • device_alarm_init() at the beginning of the per-device update loop. This will only clear the alarms for the current device,
  • device_alarm_commit() at the end of the per-device update loop. This will flush the current alarms for the current device,
  • also device_alarm_init() at the end of the per-device update loop. This will clear the current alarms, and ensure that this buffer will not be considered by other subsequent devices,
  • alarm_commit() at the end of the main update loop, for the whole daisychain. This will take care of publishing or not the "ALARM" flag in the main ups.status (device.0, root collection).