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:
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.
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 (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.
ups.alarm behavior is not yet defined (all devices alarms vs. list of device(s) that have alarms vs. nothing?)
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
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:
device.count
entry in a mapping file (see *-mib.c
)
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.
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", ... },
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
.
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.
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,
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).