How to make a new subdriver to support another SNMP device ---------------------------------------------------------- Overall concept ~~~~~~~~~~~~~~~ The SNMP protocol allow for a common way to interact with devices over the network. The NUT "snmp-ups" driver is a meta-driver that handles many SNMP devices, such as UPS and PDU. It consists of a core driver that handles most of the work of talking to the SNMP agent, and several sub-drivers to handle specific device manufacturers. Adding support for a new SNMP device is easy, because it requires only the creation of a new sub-driver. SNMP data Tree ~~~~~~~~~~~~~~ From the point of view of writing an SNMP subdriver, an SNMP device consists of a bunch of variables, called OIDs (for Object IDentifiers). Some OIDs (such as the current input voltage) are read-only, whereas others (such as the beeper enabled/disabled/muted status) can be read and written. OID are grouped together and arranged in a hierarchical tree shape, similar to directories in a file system. OID components are separated by ".", and can be expressed in numeric or textual form. For example: .iso.org.dod.internet.mgmt.mib-2.system.sysObjectID is equivalent to: .1.3.6.1.2.1.1.2.0 Here is an excerpt tree, showing only two OIDs, sysDescr and sysObjectID: .iso .org .dod .internet .mgmt .mib-2 .system .sysDescr.0 = STRING: Dell UPS Tower 1920W HV .sysObjectID.0 = OID: .iso.org.dod.internet.private.enterprises.674.10902.2 (...) .upsMIB .upsObjects .upsIdent .upsIdentModel = STRING: "Dell UPS Tower 1920W HV" (...) .private .enterprises .674 .10902 .2 .100 .1.0 = STRING: "Dell UPS Tower 1920W HV" (...) As you can see in the above example, the device name is exposed three times, through three different MIBs: - Generic MIB-II (RFC 1213): .iso.org.dod.internet.mgmt.mib-2.system.sysDescr.0 = STRING: Dell UPS Tower 1920W HV .1.3.6.1.2.1.1.1.0 = STRING: Dell UPS Tower 1920W HV - UPS MIB (RFC 1628): .iso.org.dod.internet.mgmt.mib-2.upsMIB.upsObjects.upsIdent.upsIdentModel = STRING: "Dell UPS Tower 1920W HV" .1.3.6.1.2.1.33.1.1.2.0 = STRING: "Dell UPS Tower 1920W HV" - DELL SNMP UPS MIB: .iso.org.dod.internet.private.enterprises.674.10902.2.100.1.0 = STRING: "Dell UPS Tower 1920W HV" But only the two last can serve useful data for NUT. An highly interesting OID is *sysObjectID*: its value is an OID that refers to the main MIB of the device. In the above example, the device points us at the Dell UPS MIB. *sysObjectID*, also called "sysOID" is used by snmp-ups to find the right mapping structure. For more information on SNMP, refer to the link:http://en.wikipedia.org/wiki/Simple_Network_Management_Protocol[Wikipedia] article, or browse the Internet. To be able to convert values, NUT SNMP subdrivers need to provide: - manufacturer-specific sysOID, to determine which lookup structure applies to which devices, - a mapping of SNMP variables to NUT variables, - a mapping of SNMP values to NUT values. Moreover, subdrivers might have to provide additional functionality, such as custom implementations of specific instant commands (load.off, shutdown.restart), and conversions of manufacturer specific data formats. At the time of writing this document, snmp-ups doesn't provide such mechanisms (only formatting ones), but it is planned in a future release. Creating a subdriver ~~~~~~~~~~~~~~~~~~~~ In order to create a subdriver, you will need the following: - the "MIB definition file. This file has a ".mib" extension, and is generally available on the accompanying disc, or on the manufacturer website. It should either be placed in a system directory (/usr/share/mibs/ or equivalent), or pointed using *-M* option, - a network access to the device - OR information dumps. You can create an initial "stub" subdriver for your device by using the helper script *scripts/subdriver/gen-snmp-subdriver.sh*. Note that this only creates a "stub" which MUST be customized to be useful (see CUSTOMIZATION below). You have two options to run gen-snmp-subdriver.sh: mode 1: get SNMP data from a real agent ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This method requires to have a network access to the device, in order to automatically retrieve the needed information. You have to specify the following parameters: - *-H* host address: is the SNMP host IP address or name - *-c* community: is the SNMP v1 community name (default: public)" For example: $ gen-snmp-subdriver.sh -H W.X.Y.Z -c foobar -n .mib mode 2: get data from files ^^^^^^^^^^^^^^^^^^^^^^^^^^^ This method does not require direct access to the device, at least not for the one using gen-snmp-subdriver.sh. The following SNMP data need to be dumped first: - sysOID value: for example '.1.3.6.1.4.1.705.1' - a numeric SNMP walk (OIDs in dotted numeric format) of the tree pointed by sysOID. For example: snmpwalk -On -c foobar W.X.Y.Z .1.3.6.1.4.1.705.1 > snmpwalk-On.log - a textual SNMP walk (OIDs in string format) of the tree pointed by sysOID. For example: snmpwalk -Os -c foobar W.X.Y.Z .1.3.6.1.4.1.705.1 > snmpwalk-Os.log NOTE: if the OID are only partially resolved (i.e, there are still parts expressed in numeric form), then try using *-M* to point your .mib file. Then call the script using: $ gen-snmp-subdriver.sh -s For example: $ gen-snmp-subdriver.sh -s .1.3.6.1.4.1.705.1 snmpwalk-On.log snmpwalk-Os.log This script prompts you for a name for the subdriver if you don't provide it with *-n*. Use only letters and digits, and use natural capitalization such as "Camel" (not "camel" or "CAMEL", apart if it natural). The script may prompt you for additional information. Integrating the subdriver with snmp-ups ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Beside of the mandatory customization, there are a few things that you have to do, as mentioned at the end of the script: - edit drivers/snmp-ups.h and add #include ".h", where is the name of the header file, with the *.h* extension, - edit drivers/snmp-ups.c and bump DRIVER_VERSION by adding "0.01". - also add "&" to snmp-ups.c:mib2nut[] list, where is the lower case driver name - add "-mib.c" to snmp_ups_SOURCES in drivers/Makefile.am - add "-mib.h" to dist_noinst_HEADERS in drivers/Makefile.am - copy "-mib.c" and "-mib.h" to ../drivers/ - finally call the following, from the top level directory, to test compilation: $ autoreconf && configure && make You can already start experimenting with the new subdriver; but all data will be prefixed by "unmapped.". You will now have to customize it. CUSTOMIZATION ^^^^^^^^^^^^^ The initially generated subdriver code is only a stub (mainly a big C structure to be precise), and will not implement any useful functionality (in particular, it will be unable to shut down the UPS). In the beginning, it simply attempts to monitor some UPS variables. To make this driver useful, you must examine the NUT variables of the form "unmapped.*" in the hid_info_t data structure, and map them to actual NUT variables and instant commands. There are currently no step-by-step instructions for how to do this. Please look at the files to see how the currently implemented subdrivers are written: - apc-mib.c/h - baytech-mib.c/h - bestpower-mib.c/h - compaq-mib.c/h - cyberpower-mib.c/h - eaton-*-mib.c/h - ietf-mib.c/h - mge-mib.c/h - netvision-mib.c/h - powerware-mib.c/h - raritan-pdu-mib.c - huawei-mib.c/h To help you, above each entry in -mib.c, there is a comment that displays the textual OID name. For example, the following entry: /* upsMIB.upsObjects.upsIdent.upsIdentModel = STRING: "Dell UPS Tower 1920W HV" */ { "unmapped.upsidentmodel", ST_FLAG_STRING, SU_INFOSIZE, ".1.3.6.1.4.1.2254.2.4.1.1.0", NULL, SU_FLAG_OK, NULL }, Many times, only the first field will need to be modified, to map to an actual NUT variable name. Check the <> section first to find a name that matches the OID name (closest fit). If nothing matches, contact the upsdev list, and we'll figure it out. In the above example, the right NUT variable is obviously "device.model". The MIB definition file (.mib) also contains some description of these OIDs, along with the possible enumerated values. //////////////////////////////////////////////////////////////////////////////// FIXME: Shutting down the UPS ~~~~~~~~~~~~~~~~~~~~~ It is desirable to support shutting down the UPS. Usually (for devices that follow the HID Power Device Class specification), this requires sending the UPS two commands. One for shutting down the UPS (with an 'offdelay') and one for restarting it (with an 'ondelay'), where offdelay < ondelay. The two NUT commands for which this is relevant, are 'shutdown.return' and 'shutdown.stayoff'. Since the one-to-one mapping above doesn't allow sending two HID commands to the UPS in response to sending one NUT command to the driver, this is handled by the driver. In order to make this work, you need to define the following four NUT values: ups.delay.start (variable, R/W) ups.delay.shutdown (variable, R/W) load.off.delay (command) load.on.delay (command) If the UPS supports it, the following variables can be used to show the countdown to start/shutdown: ups.timer.start (variable, R/O) ups.timer.shutdown (variable, R/O) The `load.on` and `load.off` commands will be defined implicitly by the driver (using a delay value of '0'). Define these commands yourself, if your UPS requires a different value to switch on/off the load without delay. Note that the driver expects the `load.off.delay` and `load.on.delay` to follow the HID Power Device Class specification, which means that the `load.on.delay` command should NOT switch on the load in the absence of mains power. If your UPS switches on the load regardless of the mains status, DO NOT define this command. You probably want to define the `shutdown.return` and/or `shutdown.stayoff` commands in that case. Commands defined in the subdriver will take precedence over the ones that are composed in the driver. When running the driver with the '-k' flag, it will first attempt to send a `shutdown.return` command and if that fails, will fallback to `shutdown.reboot`. ////////////////////////////////////////////////////////////////////////////////