How to make a new subdriver to support another Q* UPS
-----------------------------------------------------


Overall concept
~~~~~~~~~~~~~~~

The NUT "*nutdrv_qx*" driver is a meta-driver that handles Q* UPS devices.

It consists of a core driver that handles most of the work of talking to the hardware, and several sub-drivers to handle specific UPS manufacturers.

Adding support for a new UPS device is easy, because it requires only the creation of a new sub-driver.


Creating a subdriver
~~~~~~~~~~~~~~~~~~~~

In order to develop a new subdriver for a specific UPS you have to know the "idiom" (dialect of the protocol) spoken by that device.

This kind of devices speaks idioms that can be summed up as follows:

- We send the UPS a query for one or more information

 * If the query is supported by the device, we'll get a reply that is mostly of a fixed length, therefore, in most cases, each information starts and ends always at the same indexes

- We send the UPS a command

 * If the command is supported by the device, the UPS will either take action without any reply or reply us with a device-specific answer signaling that the command has been accepted (e.g. +ACK+)

- If the query/command isn't supported by the device we'll get either the query/command echoed back or a device-specific reply signaling that it has been rejected (e.g. +NAK+)

To be supported by this driver the idiom spoken by the UPS must comply to these conditions.


Writing a subdriver
~~~~~~~~~~~~~~~~~~~

You have to fill the +subdriver_t+ structure:

----
typedef struct {
	const char	*name;
	int		(*claim)(void);
	item_t		*qx2nut;
	void		(*initups)(void);
	void		(*initinfo)(void);
	void		(*makevartable)(void);
	const char	*accepted;
	const char	*rejected;
#ifdef TESTING
	testing_t	*testing;
#endif	/* TESTING */
} subdriver_t;
----

Where:

*+name+*::
Name of this subdriver: name of the +protocol+ that will need to be set in the +ups.conf+ file to use this subdriver plus the internal version of it separated by a space (e.g. "++Megatec 0.01++").

*+claim+*::
This function allows the subdriver to "claim" a device: return +1+ if the device is supported by this subdriver, else +0+.

*+qx2nut+*::
Main table of vars and instcmds: an array of +item_t+ mapping a UPS idiom to NUT.

*+initups+* (optional)::
Subdriver-specific +upsdrv_initups+.
This function will be called at the end of nutdrv_qx's own +upsdrv_initups+.

*+initinfo+* (optional)::
Subdriver-specific +upsdrv_initinfo+.
This function will be called at the end of nutdrv_qx's own +upsdrv_initinfo+.

*+makevartable+* (optional)::
Function to add subdriver-specific +ups.conf+ vars and flags.
Make sure not to collide with other subdrivers' vars and flags.

*+accepted+* (optional)::
String to match if the driver is expecting a reply from the UPS on instcmd/setvar in case of success.
This comparison is done after the answer we got back from the UPS has been processed to get the value we are searching, so you don't have to include the trailing carriage return (+\r+) and you can decide at which index of the answer the value should start or end setting the appropriate +from+ and +to+ in the +item_t+ (see <<_mapping_an_idiom_to_nut,Mapping an idiom to NUT>>).

*+rejected+* (optional)::
String to match if the driver is expecting a reply from the UPS in case of error.
Note that this comparison is done on the answer we got back from the UPS before it has been processed, so include also the trailing carriage return (+\r+) and whatever character is expected.

*+testing+*::
Testing table (an array of +testing_t+) that will hold the commands and the replies used for testing the subdriver.
+
--
+testing_t+:

----
typedef struct {
	const char	*cmd;
	const char	answer[SMALLBUF];
	const int	answer_len;
} testing_t;
----

Where:

*+cmd+*::
Command to match.

*+answer+*::
Answer for that command.
+
NOTE: If +answer+ contains inner ++\0++s, in order to preserve them, +answer_len+ as well as an +item_t+'s +preprocess_answer()+ function must be set.

*+answer_len+*::
Answer length:
+
- if set to +-1+ -> auto calculate answer length (treat +answer+ as a null-terminated string),
- otherwise -> use the provided length (if reasonable) and preserve inner ++\0++s (treat +answer+ as a sequence of bytes till the +item_t+'s +preprocess_answer()+ function gets called).

For more information, see <<_mapping_an_idiom_to_nut,Mapping an idiom to NUT>>.
--


Mapping an idiom to NUT
~~~~~~~~~~~~~~~~~~~~~~~

If you understand the idiom spoken by your device, you can easily map it to NUT variables and instant commands, filling +qx2nut+ with an array of +item_t+ data structure:

----
typedef struct item_t {
	const char	*info_type;
	const int	info_flags;
	info_rw_t	*info_rw;
	const char	*command;
	char		answer[SMALLBUF];
	const int	answer_len;
	const char	leading;
	char		value[SMALLBUF];
	const int	from;
	const int	to;
	const char	*dfl;
	unsigned long	qxflags;
	int		(*preprocess_command)(struct item_t *item, char *command, const size_t commandlen);
	int		(*preprocess_answer)(struct item_t *item, const int len);
	int		(*preprocess)(struct item_t *item, char *value, const size_t valuelen);
} item_t;
----

Where:

*+info_type+*::
NUT variable name, otherwise, if +QX_FLAG_NONUT+ is set, name to print to logs and if both +QX_FLAG_NONUT+ and +QX_FLAG_SETVAR+ are set, name of the var to retrieve from +ups.conf+.

*+info_flags+*::
NUT flags (+ST_FLAG_*+ values to set in +dstate_addinfo+).

*+info_rw+*::
+
--
An array of +info_rw_t+ to handle r/w variables:

- If +ST_FLAG_STRING+ is set in +info_flags+ it'll be used to set the length of the string (in +dstate_setaux+)
- If +QX_FLAG_ENUM+ is set in +qxflags+ it'll be used to set enumerated values (in +dstate_addenum+)
- If +QX_FLAG_RANGE+ is set in +qxflags+ it'll be used to set range boundaries (in +dstate_addrange+)

NOTE: If +QX_FLAG_SETVAR+ is set the value given by the user will be checked against these infos.

+info_rw_t+:

----
typedef struct {
	char	value[SMALLBUF];
	int	(*preprocess)(char *value, const size_t len);
} info_rw_t;
----

Where:

*+value+*::
Value for enum/range, or length for +ST_FLAG_STRING+.

*+preprocess(value, len)+*::
Optional function to preprocess range/enum +value+.
+
This function will be given +value+ and its +size_t+ and must return either +0+ if +value+ is supported or +-1+ if not supported.
--

*+command+*::
Command sent to the UPS to get answer, or to execute an instant command, or to set a variable.

*+answer+*::
Answer from the UPS, filled at runtime.
+
NOTE: If you expect a non-valid C string (e.g.: inner ++\0++s) or need to perform actions before the answer is used (and treated as a null-terminated string), you should set a +preprocess_answer()+ function.

*+answer_len+*::
Expected minimum length of the answer.
Set it to +0+ if there's no minimum length to look after.

*+leading+*::
Expected leading character of the answer (optional), e.g. +#+, +(+ ...

*+value+*::
Value from the answer, filled at runtime (i.e. +answer+ in the interval [+from+ to +to+]).

*+from+*::
Position of the starting character of the info we're after in the answer.

*+to+*::
Position of the ending character of the info we're after in the answer: use +0+ if all the remaining of the line is needed.

*+dfl+*::
printf format to store value from the UPS in NUT variables.
Set it either to +%s+ for strings or to a floating point specifier (e.g. +%.1f+) for numbers.
+
--

Otherwise:

- If +QX_FLAG_ABSENT+ -> default value
- If +QX_FLAG_CMD+ -> default command value
--
+
*+qxflags+*::
Driver's own flags.
+
--
[cols="m,",options="autowidth",frame="topbot",grid="rows"]
|====
|QX_FLAG_STATIC		|Retrieve this variable only once.
|QX_FLAG_SEMI_STATIC	|Retrieve this info smartly, i.e. only when a command/setvar is executed and we expect that data could have been changed.
|QX_FLAG_ABSENT		|Data is absent in the device, use default value.
|QX_FLAG_QUICK_POLL	|Mandatory vars.
|QX_FLAG_CMD		|Instant command.
|QX_FLAG_SETVAR		|The var is settable and the actual item stores info on how to set it.
|QX_FLAG_TRIM		|This var's value need to be trimmed of leading/trailing spaces/hashes.
|QX_FLAG_ENUM		|Enum values exist.
|QX_FLAG_RANGE		|Ranges for this var are available.
|QX_FLAG_NONUT		|This var doesn't have a corresponding var in NUT.
|QX_FLAG_SKIP		|Skip this var: this item won't be processed.
|====

[NOTE]
====
The driver will run a so-called +QX_WALKMODE_INIT+ in +initinfo+ walking through all the items in +qx2nut+, adding instant commands and the like.
From then on it'll run a so-called +QX_WALKMODE_QUICK_UPDATE+ just to see if the UPS is still there and then it'll do a so-called +QX_WALKMODE_FULL_UPDATE+ to update all the vars.

If there's a problem with a var in +QX_WALKMODE_INIT+, the driver will automagically set +QX_FLAG_SKIP+ on it and then it'll skip that item in +QX_WALKMODE_QUICK_UPDATE+/+QX_WALKMODE_FULL_UPDATE+, provided that the item has not the flag +QX_FLAG_QUICK_POLL+ set, in that case the driver will set +datastale+.
====
--

*+preprocess_command(item, command, commandlen)+*::
Last chance to preprocess the command to be sent to the UPS (e.g. to add CRC, ...).
This function is given the currently processed item (+item+), the command to be sent to the UPS (+command+) and its size_t (+commandlen+).
Return +-1+ in case of errors, else +0+.
+command+ must be filled with the actual command to be sent to the UPS.

*+preprocess_answer(item, len)+*::
Function to preprocess the answer we got from the UPS before we do anything else (e.g. for CRC, decoding, ...).
This function is given the currently processed item (+item+) with the answer we got from the UPS unmolested and already stored in +item+'s +answer+ and the length of that answer (+len+).
Return +-1+ in case of errors, else the length of the newly allocated +item+'s +answer+ (from now on, treated as a null-terminated string).

*+preprocess(item, value, valuelen)+*::
Function to preprocess the data from/to the UPS: you are given the currently processed item (+item+), a char array (+value+) and its +size_t+ (+valuelen+).
Return +-1+ in case of errors, else +0+.
+
--
- If +QX_FLAG_SETVAR+/+QX_FLAG_CMD+ is set then the item is processed before the command is sent to the UPS so that you can fill it with the value provided by the user.
+
NOTE: In this case +value+ must be filled with the command to be sent to the UPS.

- Otherwise the function will be used to process the value we got from the answer of the UPS before it'll get stored in a NUT variable.
+
NOTE: In this case +value+ must be filled with the processed value already compliant to NUT standards.
--

IMPORTANT: You must provide an +item_t+ with +QX_FLAG_SETVAR+ and its boundaries set for both +ups.delay.start+ and +ups.delay.shutdown+ to map the driver variables +ondelay+ and +offdelay+, as they will be used in the shutdown sequence.

TIP: In order to keep the data flow at minimum you should keep together the items in +qx2nut+ that need data from the same query (i.e. +command+): doing so the driver will send the query only once and then every +item_t+ processed after the one that got the answer, provided that it's filled with the same +command+ and that the answer wasn't +NULL+, will get that +answer+.


Examples
~~~~~~~~

The following examples are from the +voltronic+ subdriver.


Simple vars
^^^^^^^^^^^

We know that when the UPS is queried for status with +QGS\r+, it replies with something like +(234.9 50.0 229.8 50.0 000.0 000 369.1 ---.- 026.5 ---.- 018.8 100000000001\r+ and we want to access the output voltage (the third token, in this case +229.8+).

----
> [QGS\r]
< [(234.9 50.0 229.8 50.0 000.0 000 369.1 ---.- 026.5 ---.- 018.8 100000000001\r]
   0123456789012345678901234567890123456789012345678901234567890123456789012345
   0         1         2         3         4         5         6         7
----

Here's the +item_t+:

----
{ "output.voltage", 0, NULL, "QGS\r", "", 76, '(', "", 12, 16, "%.1f", 0, NULL, NULL, NULL },
----

[horizontal]
+info_type+::
+output.voltage+

+info_flags+::
+0+

+info_rw+::
+NULL+

+command+::
+QGS\r+

+answer+::
Filled at runtime

+answer_len+::
+76+

+leading+::
+(+

+value+::
Filled at runtime

+from+::
+12+ -> the index at which the info (i.e. +value+) starts

+to+::
+16+ -> the index at which the info (i.e. +value+) ends

+dfl+::
+%.1f+
+
We are expecting a number, so at first the core driver will check if it's made up entirely of digits/points/spaces, then it'll convert it into a double.
Because of that we need to provide a floating point specifier.

+qxflags+::
+0+

+preprocess_command+::
+NULL+

+preprocess_answer+::
+NULL+

+preprocess+::
+NULL+


Mandatory vars
^^^^^^^^^^^^^^

Also from +QGS\r+, we want to process the 9th status bit +10000000+*`0`*+001+ that tells us whether the UPS is shutting down or not.

----
> [QGS\r]
< [(234.9 50.0 229.8 50.0 000.0 000 369.1 ---.- 026.5 ---.- 018.8 100000000001\r]
   0123456789012345678901234567890123456789012345678901234567890123456789012345
   0         1         2         3         4         5         6         7
----

Here's the +item_t+:

----
{ "ups.status", 0, NULL, "QGS\r", "", 76, '(', "", 71, 71, "%s", QX_FLAG_QUICK_POLL, NULL, NULL, voltronic_status },
----

[horizontal]
+info_type+::
+ups.status+

+info_flags+::
+0+

+info_rw+::
+NULL+

+command+::
+QGS\r+

+answer+::
Filled at runtime

+answer_len+::
+76+

+leading+::
+(+

+value+::
Filled at runtime

+from+::
+71+ -> the index at which the info (i.e. +value+) starts

+to+::
+71+ -> the index at which the info (i.e. +value+) ends

+dfl+::
+%s+
+
Since a +preprocess+ function is defined for this item, this could have been +NULL+, however, if we want -- like here -- we can use it in our +preprocess+ function.

+qxflags+::
+QX_FLAG_QUICK_POLL+ -> this item will be polled every time the driver will check for updates.
Since this item is mandatory to run the driver, if a problem arises in +QX_WALKMODE_INIT+ the driver won't skip it and it will set +datastale+.

+preprocess_command+::
+NULL+

+preprocess_answer+::
+NULL+

+preprocess+::
+voltronic_status+
+
This function will be called *after* the +command+ has been sent to the UPS and we got back the +answer+ and stored the +value+ in order to process it to NUT standards: in this case we will convert the binary +value+ to a NUT status.


Settable vars
^^^^^^^^^^^^^

So your UPS reports its battery type when queried for +QBT\r+; we are expecting an answer like +(01\r+ and we know that the values can be mapped as follows: +00+ -> "Li", +01+ -> "Flooded" and +02+ -> "AGM".

----
> [QBT\r]
< [(01\r]	<- 00="Li", 01="Flooded" or 02="AGM"
   0123
   0
----

Here's the +item_t+:

----
{ "battery.type", ST_FLAG_RW, voltronic_e_batt_type, "QBT\r", "", 4, '(', "", 1, 2, "%s",
  QX_FLAG_SEMI_STATIC | QX_FLAG_ENUM, NULL, NULL, voltronic_p31b },
----

[horizontal]
+info_type+::
+battery.type+

+info_flags+::
+ST_FLAG_RW+ -> this is a r/w var

+info_rw+::
+voltronic_e_batt_type+
+
The values stored here will be added to the NUT variable, setting its boundaries: in this case +Li+, +Flooded+ and +AGM+ will be added as enumerated values.

+command+::
+QBT\r+

+answer+::
Filled at runtime

+answer_len+::
+4+

+leading+::
+(+

+value+::
Filled at runtime

+from+::
+1+ -> the index at which the info (i.e. +value+) starts

+to+::
+2+ -> the index at which the info (i.e. +value+) ends

+dfl+::
+%s+
+
Since a +preprocess+ function is defined for this item, this could have been +NULL+, however, if we want -- like here -- we can use it in our +preprocess+ function.

+qxflags+::
+QX_FLAG_SEMI_STATIC+ -> this item changes -- and will therefore be updated -- only when we send a command/setvar to the UPS
+
+QX_FLAG_ENUM+ -> this r/w variable is of the enumerated type and the enumerated values are listed in the +info_rw+ structure (i.e. +voltronic_e_batt_type+)

+preprocess_command+::
+NULL+

+preprocess_answer+::
+NULL+

+preprocess+::
+voltronic_p31b+
+
This function will be called *after* the +command+ has been sent to the UPS and we got back the +answer+ and stored the +value+ in order to process it to NUT standards: in this case we will check if the value is in the range and then publish the human readable form of it (i.e. +Li+, +Flooded+ or +AGM+).

We also know that we can change battery type with the +PBTnn\r+ command; we are expecting either +(ACK\r+ if the command succeeded or +(NAK\r+ if the command is rejected.

----
> [PBTnn\r]		nn = 00/01/02
< [(ACK\r]
   01234
   0
----

Here's the +item_t+:

----
{ "battery.type", 0, voltronic_e_batt_type, "PBT%02.0f\r", "", 5, '(', "", 1, 4, NULL,
  QX_FLAG_SETVAR | QX_FLAG_ENUM, NULL, NULL, voltronic_p31b_set },
----

[horizontal]
+info_type+::
+battery.type+

+info_flags+::
+0+

+info_rw+::
+voltronic_e_batt_type+
+
The value provided by the user will be automagically checked by the core nutdrv_qx driver against the enumerated values already set by the non setvar item (i.e. +Li+, +Flooded+ or +AGM+), so this could have been +NULL+, however if we want -- like here -- we can use it in our +preprocess+ function.

+command+::
+PBT%02.0f\r+

+answer+::
Filled at runtime

+answer_len+::
+5+ <- either +(NAK\r+ or +(ACK\r+

+leading+::
+(+

+value+::
Filled at runtime

+from+::
+1+ -> the index at which the info (i.e. +value+) starts

+to+::
+3+ -> the index at which the info (i.e. +value+) ends

+dfl+::
Not used for +QX_FLAG_SETVAR+

+qxflags+::
+QX_FLAG_SETVAR+ -> this item is used to set the variable +info_type+ (i.e. +battery.type+)
+
+QX_FLAG_ENUM+ -> this r/w variable is of the enumerated type and the enumerated values are listed in the +info_rw+ structure (i.e. +voltronic_e_batt_type+)

+preprocess_command+::
+NULL+

+preprocess_answer+::
+NULL+

+preprocess+::
+voltronic_p31b_set+
+
This function will be called *before* the +command+ is sent to the UPS so that we can fill +command+ with the value provided by the user: in this case the function will simply translate the human readable form of battery type (i.e. +Li+, +Flooded+ or +AGM+) to the UPS compliant type (i.e. +00+, +01+ and +02+) and then fill +value+ (the second argument passed to the +preprocess+ function).


Instant commands
^^^^^^^^^^^^^^^^

We know that we have to send to the UPS +Tnn\r+ or +T.n\r+ in order to start a battery test lasting +nn+ minutes or +.n+ minutes: we are expecting either +(ACK\r+ on success or +(NAK\r+ if the command is rejected.

----
> [Tnn\r]
< [(ACK\r]
   01234
   0
----

Here's the +item_t+:

----
{ "test.battery.start", 0, NULL, "T%s\r", "", 5, '(', "", 1, 4, NULL, QX_FLAG_CMD, NULL, NULL, voltronic_process_command },
----

[horizontal]
+info_type+::
+test.battery.start+

+info_flags+::
+0+

+info_rw+::
+NULL+

+command+::
+T%s\r+

+answer+::
Filled at runtime

+answer_len+::
+5+ <- either +(NAK\r+ or +(ACK\r+

+leading+::
+(+

+value+::
Filled at runtime

+from+::
+1+ -> the index at which the info (i.e. +value+) starts

+to+::
+3+ -> the index at which the info (i.e. +value+) ends

+dfl+::
Not used for +QX_FLAG_CMD+

+qxflags+::
+QX_FLAG_CMD+ -> this item is an instant command that will be fired when +info_type+ (i.e. +test.battery.start+) is called

+preprocess_command+::
+NULL+

+preprocess_answer+::
+NULL+

+preprocess+::
+voltronic_process_command+
+
This function will be called *before* the +command+ is sent to the UPS so that we can fill +command+ with the value provided by the user: in this case the function will check if the value is in the accepted range and then fill +value+ (the second argument passed to the +preprocess+ function) with +command+ and the given value.


Information absent in the device
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

In order to set the server-side var +ups.delay.start+, that will be then used by the driver, we have to provide the following +item_t+:

----
{ "ups.delay.start", ST_FLAG_RW, voltronic_r_ondelay, NULL, "", 0, 0, "", 0, 0, "180",
  QX_FLAG_ABSENT | QX_FLAG_SETVAR | QX_FLAG_RANGE, NULL, NULL, voltronic_process_setvar },
----

[horizontal]
+info_type+::
+ups.delay.start+

+info_flags+::
+ST_FLAG_RW+ -> this is a r/w var

+info_rw+::
+voltronic_r_ondelay+
+
The values stored here will be added to the NUT variable, setting its boundaries: in this case +0+ and +599940+ will be set as the minimum and maximum value of the variable's range.
Those values will then be used by the driver to check the user provided value.

+command+::
Not used for +QX_FLAG_ABSENT+

+answer+::
Not used for +QX_FLAG_ABSENT+

+answer_len+::
Not used for +QX_FLAG_ABSENT+

+leading+::
Not used for +QX_FLAG_ABSENT+

+value+::
Not used for +QX_FLAG_ABSENT+

+from+::
Not used for +QX_FLAG_ABSENT+

+to+::
Not used for +QX_FLAG_ABSENT+

+dfl+::
+180+ <- the default value that will be set for this variable

+qxflags+::
+QX_FLAG_ABSENT+ -> this item isn't available in the device
+
+QX_FLAG_SETVAR+ -> this item is used to set the variable +info_type+ (i.e. +ups.delay.start+)
+
+QX_FLAG_RANGE+ -> this r/w variable has a settable range and its boundaries are listed in the +info_rw+ structure (i.e. +voltronic_r_ondelay+)

+preprocess_command+::
+NULL+

+preprocess_answer+::
+NULL+

+preprocess+::
+voltronic_process_setvar+
+
This function will be called, in setvar, before the driver stores the value in the NUT var: here it's used to truncate the user-provided value to the nearest settable interval.


Information not yet available in NUT
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

If your UPS reports some data items that are not yet available as NUT variables and you need to process them, you can add them in +item_t+ data structure adding the +QX_FLAG_NONUT+ flag to its +qxflags+: the info will then be printed to the logs.

So we know that the UPS reports actual input/output phase angles when queried for +QPD\r+:

----
> [QPD\r]
< [(000 120\r]	<- Input Phase Angle -- Output Phase Angle
   012345678
   0
----

Here's the +item_t+ for input phase angle:

----
{ "input_phase_angle", 0, NULL, "QPD\r", "", 9,	'(', "", 1, 3, "%03.0f",
  QX_FLAG_STATIC | QX_FLAG_NONUT, NULL, NULL, voltronic_phase },
----

[horizontal]
+info_type+::
+input_phase_angle+
+
This information will be used to print the value we got back from the UPS in the logs.

+info_flags+::
+0+

+info_rw+::
+NULL+

+command+::
+QPD\r+

+answer+::
Filled at runtime

+answer_len+::
+9+

+leading+::
+(+

+value+::
Filled at runtime

+from+::
+1+ -> the index at which the info (i.e. +value+) starts

+to+::
+3+ -> the index at which the info (i.e. +value+) ends

+dfl+::
+%03.0f+
+
If there's no +preprocess+ function, the format is used to print the value to the logs.
Here instead it's used by the +preprocess+ function.

+qxflags+::
+QX_FLAG_STATIC+ -> this item doesn't change
+
+QX_FLAG_NONUT+ -> this item doesn't have yet a NUT variable

+preprocess_command+::
+NULL+

+preprocess_answer+::
+NULL+

+preprocess+::
+voltronic_phase+
+
This function will be called *after* the +command+ has been sent to the UPS so that we can parse the value we got back and check it.

Here's the +item_t+ for output phase angle:

----
{ "output_phase_angle", ST_FLAG_RW, voltronic_e_phase, "QPD\r",	"", 9, '(', "", 5, 7, "%03.0f",
  QX_FLAG_SEMI_STATIC | QX_FLAG_ENUM | QX_FLAG_NONUT, NULL, NULL, voltronic_phase },
----

[horizontal]
+info_type+::
+output_phase_angle+
+
This information will be used to print the value we got back from the UPS in the logs.

+info_flags+::
+ST_FLAG_RW+
+
This could also be +0+ (it's not really used by the driver), but it's set to +ST_FLAG_RW+ for cohesion with other rw vars -- also, if ever a NUT variable would become available for this item, it'll be easier to change this item and its +QX_FLAG_SETVAR+ counterpart to use it.

+info_rw+::
+voltronic_e_phase+
+
Enumerated list of available value (here: +000+, +120+, +240+ and +360+).
Since +QX_FLAG_NONUT+ is set the driver will print those values to the logs, plus you could use it in the +preprocess+ function to check the value we got back from the UPS (as done here).

+command+::
+QPD\r+

+answer+::
Filled at runtime

+answer_len+::
+9+

+leading+::
+(+

+value+::
Filled at runtime

+from+ ::
+5+ -> the index at which the info (i.e. +value+) starts

+to+::
+7+ -> the index at which the info (i.e. +value+) ends

+dfl+::
+%03.0f+
+
If there's no +preprocess+ function, the format is used to print the value to the logs.
Here instead it's used by the +preprocess+ function.

+qxflags+::
+QX_FLAG_SEMI_STATIC+ -> this item changes -- and will therefore be updated -- only when we send a command/setvar to the UPS
+
+QX_FLAG_ENUM+ -> this r/w variable is of the enumerated type and the enumerated values are listed in the +info_rw+ structure (i.e. +voltronic_e_phase+).
+
+QX_FLAG_NONUT+ -> this item doesn't have yet a NUT variable

+preprocess_command+::
+NULL+

+preprocess_answer+::
+NULL+

+preprocess+::
+voltronic_phase+
+
This function will be called *after* the +command+ has been sent to the UPS so that we can parse the value we got back and check it.
Here it's used also to store a var that will then be used to check the value in setvar's preprocess function.

If you need also to change some values in the UPS you can add a +ups.conf+ var/flag in the subdriver's own +makevartable+ and then process it adding to its +qxflags+ both +QX_FLAG_NONUT+ and +QX_FLAG_SETVAR+: this item will be processed only once in +QX_WALKMODE_INIT+.

The driver will check if the var/flag is defined in +ups.conf+: if so, it'll then call +setvar+ passing to this item the defined value, if any, and then it'll print the results in the logs.

We know we can set output phase angle sending +PPDnnn\r+ to the UPS:

----
> [PPDn\r]		n = (000, 120, 180 or 240)
< [(ACK\r]
   01234
   0
----

Here's the +item_t+

----
{ "output_phase_angle", 0, voltronic_e_phase, "PPD%03.0f\r", "", 5, '(', "", 1, 4, NULL,
  QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT, NULL, NULL, voltronic_phase_set },
----

[horizontal]
+info_type+::
+output_phase_angle+
+
This information will be used to print the value we got back from the UPS in the logs and to retrieve the user-provided value in +ups.conf+.
So, name it after the variable you created to use in +ups.conf+ in the subdriver's own +makevartable+.

+info_flags+::
+0+

+info_rw+::
+voltronic_e_phase+
+
Enumerated list of available values (here: +000+, +120+, +240+ and +360+).
The value provided by the user will be automagically checked by the core nutdrv_qx driver against the enumerated values stored here.

+command+::
+PPD%03.0f\r+

+answer+::
Filled at runtime

+answer_len+::
+5+ <- either +(NAK\r+ or +(ACK\r+

+leading+::
+(+

+value+::
Filled at runtime

+from+::
+1+ -> the index at which the info (i.e. +value+) starts

+to+::
+3+ -> the index at which the info (i.e. +value+) ends

+dfl+::
Not used for +QX_FLAG_SETVAR+

+qxflags+::
+QX_FLAG_SETVAR+ -> this item is used to set the variable +info_type+ (i.e. +output_phase_angle+)
+
+QX_FLAG_ENUM+ -> this r/w variable is of the enumerated type and the enumerated values are listed in the +info_rw+ structure (i.e. +voltronic_e_phase+).
+
+QX_FLAG_NONUT+ -> this item doesn't have yet a NUT variable

+preprocess_command+::
+NULL+

+preprocess_answer+::
+NULL+

+preprocess+::
+voltronic_phase_set+
+
This function will be called *before* the +command+ is sent to the UPS so that we can check user-provided value and fill +command+ with it and then fill +value+ (the second argument passed to the +preprocess+ function).


Support functions
~~~~~~~~~~~~~~~~~

You are already given the following functions:

*+int	instcmd(const char *cmdname, const char *extradata)+*::
Execute an instant command. In detail:
+
--
- look up the given +cmdname+ in the qx2nut data structure (if not found, try to fallback to commonly known commands);
- if +cmdname+ is found, call its preprocess function, passing to it +extradata+, if any, otherwise its +dfl+ value, if any;
- send the command to the device and check the reply.
--
+
Return +STAT_INSTCMD_INVALID+ if the command is invalid, +STAT_INSTCMD_FAILED+ if it failed, +STAT_INSTCMD_HANDLED+ on success.

*+int	setvar(const char *varname, const char *val)+*::
Set r/w variable to a value after it has been checked against its +info_rw+ structure.
Return +STAT_SET_HANDLED+ on success, otherwise +STAT_SET_UNKNOWN+.

*+item_t	*find_nut_info(const char *varname, const unsigned long flag, const unsigned long noflag)+*::
Find an item of +item_t+ type in +qx2nut+ data structure by its +info_type+, optionally filtered by its +qxflags+, and return it if found, otherwise return +NULL+.
 - +flag+: flags that have to be set in the item, i.e. if one of the flags is absent in the item it won't be returned.
 - +noflag+: flags that have to be absent in the item, i.e. if at least one of the flags is set in the item it won't be returned.

*+int	qx_process(item_t *item, const char *command)+*::
Send +command+ (a null-terminated byte string) or, if it is +NULL+, send the command stored in the +item+ to the UPS and process the reply, saving it in +item+'s +answer+.
Return +-1+ on errors, +0+ on success.

*+int	ups_infoval_set(item_t *item)+*::
Process the value we got back from the UPS (set status bits and set the value of other parameters), calling the +item+-specific +preprocess+ function, if any, otherwise executing the standard preprocessing (including trimming if +QX_FLAG_TRIM+ is set).
Return +-1+ on failure, +0+ for a status update and +1+ in all other cases.

*+int	qx_status(void)+*::
Return the currently processed status so that it can be checked with one of the +status_bit_t+ passed to the +STATUS()+ macro (see +nutdrv_qx.h+).

*+void	update_status(const char *nutvalue)+*::
If you need to edit the current status call this function with one of the NUT status (all but +OB+ are supported, simply set it as not +OL+); prefix them with an exclamation mark if you want to clear them from the status (e.g. +!OL+).


Armac Subdriver
~~~~~~~~~~~~~~~

Armac subdriver is based on reverse engineering of Power Manager II software by
Richcomm Technologies written in 2005 that is still (as of 2023) being
distributed as a valid software for freshly sold UPS of various manufacturers.
It uses commands as defined for Megatec protocol - but has a different
communication mechanism.

It uses two types of USB interrupt transfers:
- 4 bytes to send a command (usually single transfer).
- 6 byte chunk to read a reply (multiple transfers).

Transfers are similar to those of the richcomm nut driver, but the transferred
data is not short binary commands. Instead, serial text data is overlaid in
these transfers in a way that creates a badly made USB serial interface.  UPS
reply looks similar to this:

 0  1  2  3  4  5
HL 00 00 00 00 00

HL is a control byte. Its high nibble meaning is unknown. It changes between
two possible values during transmission. Low nibble encodes number of bytes
that have a meaning in the transaction. For example there are 5 bytes that
might contain ASCII serial data, but only some might be valid, and other might
be random, stale buffer data, etc.

What follows is set of observed transmissions by various UPSes gathered from
Github issues.

Transfer dumps
^^^^^^^^^^^^^^

#### Vultech V2000

----
419.987514     [D4] armac command Q1
419.988307     [D4] armac cleanup ret i=0 ret=6 ctrl=c0
420.119402     [D4] read: ret 6 buf 81: 28 30 31 30 30  >(0100<
420.130383     [D4] read: ret 6 buf c1: 32 30 31 30 30  >20100<
420.141408     [D4] read: ret 6 buf 82: 33 33 31 30 30  >33100<
420.152201     [D4] read: ret 6 buf c3: 2e 30 20 30 30  >.0 00<
420.153237     [D4] read: ret 6 buf 82: 30 30 20 30 30  >00 00<
420.164299     [D4] read: ret 6 buf c1: 30 30 20 30 30  >00 00<
420.175293     [D4] read: ret 6 buf 82: 2e 30 20 30 30  >.0 00<
420.186358     [D4] read: ret 6 buf c3: 20 32 33 30 30  > 2300<
420.190322     [D4] read: ret 6 buf 83: 33 2e 30 30 30  >3.000<
420.194323     [D4] read: ret 6 buf c1: 20 2e 30 30 30  > .000<
420.205358     [D4] read: ret 6 buf 81: 30 2e 30 30 30  >0.000<
420.216318     [D4] read: ret 6 buf c2: 31 34 30 30 30  >14000<
420.227445     [D4] read: ret 6 buf 83: 20 34 39 30 30  > 4900<
420.228334     [D4] read: ret 6 buf c2: 2e 30 39 30 30  >.0900<
420.239461     [D4] read: ret 6 buf 81: 20 30 39 30 30  > 0900<
420.250411     [D4] read: ret 6 buf c2: 32 37 39 30 30  >27900<
420.261405     [D4] read: ret 6 buf 83: 2e 30 20 30 30  >.0 00<
420.265468     [D4] read: ret 6 buf c3: 32 30 2e 30 30  >20.00<
420.269465     [D4] read: ret 6 buf 81: 38 30 2e 30 30  >80.00<
420.280322     [D4] read: ret 6 buf c1: 20 30 2e 30 30  > 0.00<
420.291469     [D4] read: ret 6 buf 82: 30 30 2e 30 30  >00.00<
420.302465     [D4] read: ret 6 buf c3: 30 30 31 30 30  >00100<
420.303511     [D4] read: ret 6 buf 82: 00 30 31 30 30  >          <- This has 0x00 and '0', will be read as "00"
420.303515     [D3] found null byte in status bits at 43 byte, assuming 0.
420.314425     [D4] read: ret 6 buf c1: 31 30 31 30 30  >10100<    <- this has '1'
420.325432     [D4] read: ret 6 buf 81: 0d 30 31 30 30  >.0100<    <- and this finishes with `\r`.
420.325442     [D3] armac command Q1 response read: '(233.0 000.0 233.0 014 49.0 27.0 20.8 00001001'
----

----
1.185164     [D4] armac command ID
1.316257     [D4] read: ret 6 buf c1: 23 31 00 30 30  >#1
1.327309     [D4] read: ret 6 buf 81: 20 31 00 30 30  > 1
1.338264     [D4] read: ret 6 buf c2: 20 20 00 30 30  >
1.349151     [D4] read: ret 6 buf 83: 20 20 20 30 30  >   00<
1.360277     [D4] read: ret 6 buf c2: 20 20 20 30 30  >   00<
1.371322     [D4] read: ret 6 buf 83: 20 20 20 30 30  >   00<
1.382265     [D4] read: ret 6 buf c3: 20 20 20 30 30  >   00<
1.393156     [D4] read: ret 6 buf 82: 20 20 20 30 30  >   00<
1.404324     [D4] read: ret 6 buf c3: 20 20 20 30 30  >   00<
1.415342     [D4] read: ret 6 buf 83: 20 20 20 30 30  >   00<
1.426292     [D4] read: ret 6 buf c2: 20 20 20 30 30  >   00<
1.437203     [D4] read: ret 6 buf 83: 20 20 20 30 30  >   00<
1.448328     [D4] read: ret 6 buf c3: 56 34 2e 30 30  >V4.00<
1.459293     [D4] read: ret 6 buf 82: 31 30 2e 30 30  >10.00<
1.470274     [D4] read: ret 6 buf c3: 20 20 20 30 30  >   00<
1.481208     [D4] read: ret 6 buf 82: 20 20 20 30 30  >   00<
1.492261     [D4] read: ret 6 buf c1: 0d 20 20 30 30  >
1.492270     [D3] armac command ID response read: '#                           V4.10     '
----

----
4.749667     [D4] armac command F
4.876638     [D4] read: ret 6 buf 81: 23 31 00 30 30  >#1
4.887614     [D4] read: ret 6 buf c1: 32 31 00 30 30  >21
4.898644     [D4] read: ret 6 buf 82: 32 30 00 30 30  >20
4.909595     [D4] read: ret 6 buf c3: 2e 30 20 30 30  >.0 00<
4.920648     [D4] read: ret 6 buf 82: 30 30 20 30 30  >00 00<
4.931629     [D4] read: ret 6 buf c3: 35 20 32 30 30  >5 200<
4.942601     [D4] read: ret 6 buf 83: 34 2e 30 30 30  >4.000<
4.953666     [D4] read: ret 6 buf c2: 30 20 30 30 30  >0 000<
4.964535     [D4] read: ret 6 buf 83: 35 30 2e 30 30  >50.00<
4.975540     [D4] read: ret 6 buf c2: 30 0d 2e 30 30  >0
4.975546     [D3] armac command F response read: '#220.0 005 24.00 50.0'
----

#### Armac R/2000I/PSW

----
112.966856     [D4] armac command Q1
112.968197     [D4] armac cleanup ret i=0 ret=6 ctrl=c0         <- Cleanups required.
113.091193     [D4] read: ret 6 buf 81: 28 30 0d 2e 30  >(0     <- Usually 1-3 bytes available in transfer.
113.103211     [D4] read: ret 6 buf c1: 30 30 0d 2e 30  >00
113.115180     [D4] read: ret 6 buf 82: 30 30 0d 2e 30  >00
113.117144     [D4] read: ret 6 buf c3: 2e 30 20 2e 30  >.0 .0<
113.120150     [D4] read: ret 6 buf 81: 31 30 20 2e 30  >10 .0<
113.132178     [D4] read: ret 6 buf c1: 34 30 20 2e 30  >40 .0<
113.144159     [D4] read: ret 6 buf 82: 30 2e 20 2e 30  >0. .0<
113.146149     [D4] read: ret 6 buf c3: 30 20 32 2e 30  >0 2.0<
113.149173     [D4] read: ret 6 buf 81: 32 20 32 2e 30  >2 2.0<
113.161167     [D4] read: ret 6 buf c1: 37 20 32 2e 30  >7 2.0<
113.173159     [D4] read: ret 6 buf 82: 2e 30 32 2e 30  >.02.0<
113.175157     [D4] read: ret 6 buf c3: 20 30 30 2e 30  > 00.0<
113.178158     [D4] read: ret 6 buf 81: 32 30 30 2e 30  >200.0<
113.190157     [D4] read: ret 6 buf c1: 20 30 30 2e 30  > 00.0<
113.202161     [D4] read: ret 6 buf 82: 30 30 30 2e 30  >000.0<
113.204154     [D4] read: ret 6 buf c3: 2e 30 20 2e 30  >.0 .0<
113.207150     [D4] read: ret 6 buf 81: 34 30 20 2e 30  >40 .0<
113.219174     [D4] read: ret 6 buf c1: 36 30 20 2e 30  >60 .0<
113.231165     [D4] read: ret 6 buf 82: 2e 38 20 2e 30  >.8 .0<
113.233157     [D4] read: ret 6 buf c3: 20 35 36 2e 30  > 56.0<
113.237149     [D4] read: ret 6 buf 81: 2e 35 36 2e 30  >.56.0<
113.249168     [D4] read: ret 6 buf c1: 30 35 36 2e 30  >056.0<
113.261155     [D4] read: ret 6 buf 83: 20 31 30 2e 30  > 10.0<
113.263151     [D4] read: ret 6 buf c2: 30 30 30 2e 30  >000.0<
113.266152     [D4] read: ret 6 buf 81: 31 30 30 2e 30  >100.0<
113.278161     [D4] read: ret 6 buf c1: 30 30 30 2e 30  >000.0<  <- No Null bytes.
113.290155     [D4] read: ret 6 buf 82: 30 30 30 2e 30  >000.0<
113.292159     [D4] read: ret 6 buf c1: 0d 30 30 2e 30  >
113.292169     [D3] armac command Q1 response read: '(000.0 140.0 227.0 002 00.0 46.8 56.0 10001000'
----

Next query would return 0x80 control byte - 0 available bytes. This used to
terminate transmission, but some UPS don't work like that.


#### Armac R/3000I/PF1

----
0.083301     [D4] armac command Q1
0.164847     [D4] read: ret 6 buf a6: 28 32 34 31 2e  >(241.<
0.184839     [D4] read: ret 6 buf 86: 35 20 30 30 30  >5 000<
0.205851     [D4] read: ret 6 buf a6: 2e 30 20 32 33  >.0 23<
0.226849     [D4] read: ret 6 buf 86: 30 2e 33 20 30  >0.3 0<
0.247859     [D4] read: ret 6 buf a6: 30 30 20 34 39  >00 49<
0.268862     [D4] read: ret 6 buf 86: 2e 39 20 32 2e  >.9 2.<
0.289857     [D4] read: ret 6 buf a6: 32 35 20 34 38  >25 48<
0.309866     [D4] read: ret 6 buf 86: 2e 30 20 30 30  >.0 00<
0.330863     [D4] read: ret 6 buf a6: 30 30 30 30 30  >00000<
0.827913     [D4] read: ret 6 buf 83: 31 0d 30 30 30  >1 000<
0.827927     [D3] armac command Q1 response read: '(241.5 000.0 230.3 000 49.9 2.25 48.0 00000001'
0.827954     [D4] armac command ID
1.394985     [D4] read: ret 6 buf a5: 4e 41 4b 0d 30  >NAK  <
1.395001     [D3] armac command ID response read: 'NAK'
----

This UPS sends higher nibble set to 6 often, which exceeds available bytes.
Maybe means that more are available. Its serial-USB bridge is probably faster.
We read 5 bytes in case 6 nibble is sent. End of transmission is marked by `\r`,
no 0 nibble is sent.


Notes
~~~~~

You must put the generated files into the +drivers/+ subdirectory, with the name of your subdriver preceded by +nutdrv_qx_+, and update +nutdrv_qx.c+ by adding the appropriate +#include+ line and by updating the definition of +subdriver_list+.

Please, make sure to add your driver in that list in a smart way: if your device supports also the basic commands used by the other subdrivers to claim a device, add something that is unique (i.e. not supported by the other subdrivers) to your device in your claim function and then add it on top of the slightly supported ones in that list.

You must also add the subdriver to +NUTDRV_QX_SUBDRIVERS+ list variable in the +drivers/Makefile.am+ and call "++autoreconf++" and/or "++./configure++" from the top level NUT directory.

You can then recompile +nutdrv_qx+, and start experimenting with the new subdriver.

For more details, have a look at the currently available subdrivers:

- +nutdrv_qx_bestups.+{+c+,+h+}
- +nutdrv_qx_masterguard.+{+c+,+h+}
- +nutdrv_qx_mecer.+{+c+,+h+}
- +nutdrv_qx_megatec.+{+c+,+h+}
- +nutdrv_qx_megatec-old.+{+c+,+h+}
- +nutdrv_qx_mustek.+{+c+,+h+}
- +nutdrv_qx_q1.+{+c+,+h+}
- +nutdrv_qx_voltronic.+{+c+,+h+}
- +nutdrv_qx_voltronic-qs.+{+c+,+h+}
- +nutdrv_qx_voltronic-qs-hex.+{+c+,+h+}
- +nutdrv_qx_zinto.+{+c+,+h+}
- +nutdrv_qx_ablerex.+{+c+,+h+}

