How to make a new subdriver to support another SNMP device
----------------------------------------------------------

//////////////////////////////////////////////////////////////////////////////
// You can find a rendered variant of this document on the web at
// https://networkupstools.org/docs/developer-guide.chunked/new-drivers.html#snmp-subdrivers
//////////////////////////////////////////////////////////////////////////////

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 name>.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 <sysOID value> <numeric SNMP walk> <string SNMP walk>

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 "<HFILE>.h", where <HFILE> 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 "&<LDRIVER>" to snmp-ups.c:mib2nut[] list, where <LDRIVER> is the
lower case driver name
- add "<LDRIVER>-mib.c" to snmp_ups_SOURCES in drivers/Makefile.am
- add "<LDRIVER>-mib.h" to dist_noinst_HEADERS in drivers/Makefile.am
- copy "<LDRIVER>-mib.c" and "<LDRIVER>-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 (commonly wrapped into `snmp_info_default()`
macros for portability), 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 source files to see how the currently
implemented SNMP 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 <LDRIVER>-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" */
	snmp_info_default("ups.model", 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 <<nut-names,NUT command and variable naming scheme>> 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.

NOTE: To test existing data points (including those not yet translated
to standard NUT mappings conforming to <<nut-names,NUT command and
variable naming scheme>>), you can use custom drivers built after you
`./configure --with-unmapped-data-points`.
Production driver builds must not include any non-standard names.

////////////////////////////////////////////////////////////////////////////////
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`.

////////////////////////////////////////////////////////////////////////////////
