ifndef::external_title[] [[daisychain]] NUT daisychain support notes ============================ endif::external_title[] NUT supports daisychained devices for any kind of device that proposes it. This chapter introduces: * for developers: how to implement such mechanism, * for users: how to manage and use daisychained devices in NUT in general, and how to take advantage of the provided features. Introduction ------------ It's not unusual to see some daisy-chained PDUs or UPSs, connected together in master-slave mode, to only consume 1 IP address for their communication interface (generally, network card exposing SNMP data) and expose only one point of communication to manage several devices, through the daisy-chain master. This breaks the historical consideration of NUT that one driver provides data for one unique device. However, there is an actual need, and a smart approach was considered to fulfill this, while not breaking the standard scope (for base compatibility). 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..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..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). ======