Skip to content

Commit

Permalink
Merge pull request #2818 from turekl/master
Browse files Browse the repository at this point in the history
Add nutdrv_qx driver for Gtec ZP120N
  • Loading branch information
jimklimov authored Feb 27, 2025
2 parents 36ee76d + deb22b0 commit 6c6f453
Show file tree
Hide file tree
Showing 7 changed files with 380 additions and 10 deletions.
2 changes: 2 additions & 0 deletions NEWS.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ https://github.com/networkupstools/nut/milestone/11
* introduced `innovart31` protocol support for Innova RT 3/1 UPSes. [#2712, #2798]
* introduced `q2` and `q6` protocol support; currently also based/tested
on Innova devices, but other models than RT 3/1. [#2798]
* introduced a `gtec` subdriver and protocol, tested over USB with a
Gtec ZP120N device. [#2818]
* extended Voltronic protocol to support longer numbers as remaining
`battery.runtime` value. [#2765]
Expand Down
40 changes: 36 additions & 4 deletions docs/man/nutdrv_qx.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ refers to this large family of similar dialects as the 'Megatec Q*' or

The *nutdrv_qx* driver is known to work with various UPSes from
'Armac', 'Blazer', 'Energy Sistem', 'Fenton Technologies', 'General Electric',
'Hunnox', 'Masterguard', 'Mustek', 'Powercool', 'Voltronic Power', 'SKE'
(rebranded by many, many -- have I said many? -- others...
'Gtec', 'Hunnox', 'Masterguard', 'Mustek', 'Powercool', 'Voltronic Power',
'SKE' (rebranded by many, many -- have I said many? -- others...

Long story short: if your UPS came with a software called 'Viewpower',
chances are high that it works with this driver with one of the
Expand Down Expand Up @@ -79,7 +79,7 @@ If you set stayoff in linkman:ups.conf[5] when FSD arises the UPS will call a *s

*protocol =* 'string'::
Skip autodetection of the protocol to use and only use the one specified.
Supported values: 'bestups', 'hunnox', 'innovart31', 'masterguard', 'mecer', 'megatec', 'megatec/old', 'mustek', 'q1', 'q2', 'q6', 'voltronic', 'voltronic-qs', 'voltronic-qs-hex' and 'zinto'.
Supported values: 'bestups', 'gtec', 'hunnox', 'innovart31', 'masterguard', 'mecer', 'megatec', 'megatec/old', 'mustek', 'q1', 'q2', 'q6', 'voltronic', 'voltronic-qs', 'voltronic-qs-hex' and 'zinto'.
+
Run the driver program with the `--help` option to see the exact list of
`protocol` values it would currently recognize.
Expand Down Expand Up @@ -374,7 +374,7 @@ include::nut_usb_addvars.txt[]

*subdriver =* 'string'::
Select a serial-over-USB subdriver to use.
You have a choice between *cypress*, *fabula*, *fuji*, *hunnox*, *ippon*, *krauler*, *phoenix*, *phoenixtec*, *sgs*, *snr*, *armac* and *ablerex*.
You have a choice between *ablerex*, *armac*, *cypress*, *fabula*, *fuji*, *gtec*, *hunnox*, *ippon*, *krauler*, *phoenix*, *phoenixtec*, *sgs* and *snr*.
+
Run the driver program with the `--help` option to see the exact list of
`subdriver` values it would currently recognize.
Expand Down Expand Up @@ -405,6 +405,38 @@ the older standalone `richcomm_usb` driver.
This subdriver, meant to be used with the 'megatec' protocol, does *not* support the various *test.battery* commands.
Plus, the *shutdown.return* command ignores the values set in 'ups.delay.start'/*ondelay* and makes the UPS turn on the load as soon as power is back.

*'gtec' subdriver*::
Currently, the Gtec specific support is only known to work with USB devices
(tested with a Gtec ZP120N), and was not seen with Serial port.
+
This mode is not automatically detected, and should be enabled manually in
your 'ups.conf', e.g.:
+
----
[gtec-ups]
driver = "nutdrv_qx"
port = "auto"
subdriver = "gtec"
protocol = "gtec"
----
+
Other subdrivers and protocol implementations (including linkman:blazer_usb[8])
sort of work, but both have two problems:
+
* They use the simple "Q1" query, which doesn't report the result of
the latest battery test.
- For some reason the UPS reports normal battery voltage even when
the battery is completely disconnected.
So you won't know the battery is dead until a power failure.
- This driver sends the more advanced "Q4" request instead.
Here the answer includes status letters with more information
than the Q1 binary flags, including battery status.
* USB reply is read in 8-byte chunks, which causes the UPS to disconnect
from USB. The UPS reconnects in a second, but it still breaks the
initialization of `nutdrv_qx`, where the query is sent twice in a
short time (unlike `blazer_usb`).
- The solution is simple: read the whole reply at once.

*'hunnox' subdriver*::
This protocol subdriver is closely related to 'fabula' one, with a few tweaks for devices not directly supported by that driver.

Expand Down
4 changes: 3 additions & 1 deletion docs/nut.dict
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
personal_ws-1.1 en 3317 utf-8
personal_ws-1.1 en 3319 utf-8
AAC
AAS
ABI
Expand Down Expand Up @@ -1440,6 +1440,7 @@ Yoyodyne
Yukai
Yunto
ZFS
ZP
ZProject
Zaika
Zampieri
Expand Down Expand Up @@ -2035,6 +2036,7 @@ gpiochip
graphviz
groupadd
groupname
gtec
gtk
guesstimate
guesstimation
Expand Down
6 changes: 2 additions & 4 deletions drivers/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ NUTDRV_QX_SUBDRIVERS = nutdrv_qx_bestups.c nutdrv_qx_blazer-common.c \
nutdrv_qx_mecer.c nutdrv_qx_megatec.c nutdrv_qx_megatec-old.c \
nutdrv_qx_mustek.c nutdrv_qx_q1.c nutdrv_qx_q2.c nutdrv_qx_q6.c nutdrv_qx_voltronic.c \
nutdrv_qx_voltronic-qs.c nutdrv_qx_voltronic-qs-hex.c nutdrv_qx_zinto.c \
nutdrv_qx_hunnox.c nutdrv_qx_ablerex.c
nutdrv_qx_hunnox.c nutdrv_qx_ablerex.c nutdrv_qx_gtec.c
nutdrv_qx_SOURCES += $(NUTDRV_QX_SUBDRIVERS)

# ----------------------------------------------------------------------
Expand All @@ -405,9 +405,7 @@ dist_noinst_HEADERS = \
upshandler.h usb-common.h usbhid-ups.h powercom-hid.h compaq-mib.h idowell-hid.h \
apcsmart.h apcsmart_tabs.h apcsmart-old.h apcupsd-ups.h cyberpower-mib.h riello.h openups-hid.h \
delta_ups-mib.h nutdrv_qx.h nutdrv_qx_bestups.h nutdrv_qx_blazer-common.h \
nutdrv_qx_innovart31.h \
nutdrv_qx_masterguard.h \
nutdrv_qx_mecer.h nutdrv_qx_ablerex.h \
nutdrv_qx_gtec.h nutdrv_qx_innovart31.h nutdrv_qx_masterguard.h nutdrv_qx_mecer.h nutdrv_qx_ablerex.h \
nutdrv_qx_megatec.h nutdrv_qx_megatec-old.h nutdrv_qx_mustek.h nutdrv_qx_q1.h nutdrv_qx_q2.h nutdrv_qx_q6.h nutdrv_qx_hunnox.h \
nutdrv_qx_voltronic.h nutdrv_qx_voltronic-qs.h nutdrv_qx_voltronic-qs-hex.h nutdrv_qx_zinto.h \
upsdrvquery.h \
Expand Down
78 changes: 77 additions & 1 deletion drivers/nutdrv_qx.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
# define DRIVER_NAME "Generic Q* Serial driver"
#endif /* QX_USB */

#define DRIVER_VERSION "0.40"
#define DRIVER_VERSION "0.41"

#ifdef QX_SERIAL
# include "serial.h"
Expand All @@ -85,6 +85,7 @@
#include "nutdrv_qx_zinto.h"
#include "nutdrv_qx_masterguard.h"
#include "nutdrv_qx_ablerex.h"
#include "nutdrv_qx_gtec.h"

/* Reference list of available non-USB subdrivers */
static subdriver_t *subdriver_list[] = {
Expand All @@ -103,6 +104,7 @@ static subdriver_t *subdriver_list[] = {
&innovart31_subdriver,
&q2_subdriver,
&q6_subdriver,
&gtec_subdriver,
/* Fallback Q1 subdriver */
&q1_subdriver,
NULL
Expand Down Expand Up @@ -1870,6 +1872,79 @@ static void *ablerex_subdriver_fun(USBDevice_t *device)
return NULL;
}

/* Gtec communication subdriver (based on Cypress) */
static int gtec_command(const char *cmd, char *buf, size_t buflen)
{
char tmp[SMALLBUF];
int ret = 0;
size_t i;

if (buflen > INT_MAX) {
upsdebugx(3, "%s: requested to read too much (%" PRIuSIZE "), "
"reducing buflen to (INT_MAX-1)",
__func__, buflen);
buflen = (INT_MAX - 1);
}

/* Send command */
memset(tmp, 0, sizeof(tmp));
snprintf(tmp, sizeof(tmp), "%s", cmd);

for (i = 0; i < strlen(tmp); i += (size_t)ret) {

/* Write data in 8-byte chunks */
/* ret = usb->set_report(udev, 0, (unsigned char *)&tmp[i], 8); */
ret = usb_control_msg(udev,
USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE,
0x09, 0x02, 0,
(usb_ctrl_charbuf)&tmp[i], 8, 5000);

if (ret <= 0) {
upsdebugx(3, "send: %s (%d)",
ret ? nut_usb_strerror(ret) : "timeout",
ret);
return ret;
}

}

upsdebugx(3, "send: %.*s", (int)strcspn(tmp, "\r"), tmp);

/* Read reply */
memset(buf, 0, buflen);

for (i = 0; (i <= buflen-128) && (memchr(buf, '\r', buflen) == NULL); i += (size_t)ret) {

/* Read data in 8-byte chunks */
/* ret = usb->get_interrupt(udev, (unsigned char *)&buf[i], 8, 1000); */
ret = usb_interrupt_read(udev,
0x81,
(usb_ctrl_charbuf)&buf[i], 128, 1000);

/* Any errors here mean that we are unable to read a reply
* (which will happen after successfully writing a command
* to the UPS) */
if (ret <= 0) {
upsdebugx(3, "read: %s (%d)",
ret ? nut_usb_strerror(ret) : "timeout",
ret);
return ret;
}

snprintf(tmp, sizeof(tmp), "read [% 3d]", (int)i);
upsdebug_hex(5, tmp, &buf[i], (size_t)ret);

}

upsdebugx(3, "read: %.*s", (int)strcspn(buf, "\r"), buf);

if (i > INT_MAX) {
upsdebugx(3, "%s: read too much (%" PRIuSIZE ")", __func__, i);
return -1;
}
return (int)i;
}

static struct {
bool_t initialized;
bool_t ok;
Expand Down Expand Up @@ -2929,6 +3004,7 @@ void upsdrv_shutdown(void)
{ "snr", &snr_command },
{ "ablerex", &ablerex_command },
{ "armac", &armac_command },
{ "gtec", &gtec_command },
{ NULL, NULL }
};
# endif
Expand Down
Loading

0 comments on commit 6c6f453

Please sign in to comment.