Skip to content

Commit

Permalink
Add support for Hunnox HNX-850
Browse files Browse the repository at this point in the history
  • Loading branch information
mjan committed Dec 1, 2018
1 parent 0deb6a6 commit d8eaf12
Show file tree
Hide file tree
Showing 4 changed files with 286 additions and 4 deletions.
3 changes: 2 additions & 1 deletion drivers/Makefile.am
Expand Up @@ -260,7 +260,8 @@ endif
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_voltronic.c \
nutdrv_qx_voltronic-qs.c nutdrv_qx_voltronic-qs-hex.c nutdrv_qx_zinto.c
nutdrv_qx_voltronic-qs.c nutdrv_qx_voltronic-qs-hex.c nutdrv_qx_zinto.c \
nutdrv_qx_hunnox.c
nutdrv_qx_SOURCES += $(NUTDRV_QX_SUBDRIVERS)

# ----------------------------------------------------------------------
Expand Down
121 changes: 118 additions & 3 deletions drivers/nutdrv_qx.c
Expand Up @@ -63,6 +63,7 @@
/* == Subdrivers == */
/* Include all known subdrivers */
#include "nutdrv_qx_bestups.h"
#include "nutdrv_qx_hunnox.h"
#include "nutdrv_qx_mecer.h"
#include "nutdrv_qx_megatec.h"
#include "nutdrv_qx_megatec-old.h"
Expand All @@ -84,6 +85,7 @@ static subdriver_t *subdriver_list[] = {
&mecer_subdriver,
&megatec_subdriver,
&zinto_subdriver,
&hunnox_subdriver,
/* Fallback Q1 subdriver */
&q1_subdriver,
NULL
Expand Down Expand Up @@ -126,6 +128,8 @@ static bool_t data_has_changed = FALSE; /* for SEMI_STATIC data polling */

static time_t lastpoll; /* Timestamp the last polling */

static int hunnox_step = 0;

#if defined(QX_USB) && defined(QX_SERIAL)
static int is_usb = 0; /* Whether the device is connected through USB (1) or serial (0) */
#endif /* QX_USB && QX_SERIAL */
Expand Down Expand Up @@ -680,6 +684,53 @@ static int ippon_command(const char *cmd, char *buf, size_t buflen)
return (int)len;
}

static int hunnox_protocol(int asking_for)
{
char buf[1030];

int langid_fix_local = 0x0409;

if (langid_fix != -1) {
langid_fix_local = langid_fix;
}

switch (hunnox_step) {
case 0:
upsdebugx(3, "asking for: %02X", 0x00);
usb_get_string(udev, 0x00, langid_fix_local, buf, 1026);
usb_get_string(udev, 0x00, langid_fix_local, buf, 1026);
usb_get_string(udev, 0x01, langid_fix_local, buf, 1026);
usleep(10000);
break;
case 1:
if (asking_for != 0x0d) {
upsdebugx(3, "asking for: %02X", 0x0d);
usb_get_string(udev, 0x0d, langid_fix_local, buf, 102);
}
break;
case 2:
if (asking_for != 0x03) {
upsdebugx(3, "asking for: %02X", 0x03);
usb_get_string(udev, 0x03, langid_fix_local, buf, 102);
}
break;
case 3:
if (asking_for != 0x0c) {
upsdebugx(3, "asking for: %02X", 0x0c);
usb_get_string(udev, 0x0c, langid_fix_local, buf, 102);
}
break;
default:
hunnox_step = 0;
}
hunnox_step++;
if (hunnox_step > 3) {
hunnox_step = 1;
}

return 0;
}

/* Krauler communication subdriver */
static int krauler_command(const char *cmd, char *buf, size_t buflen)
{
Expand Down Expand Up @@ -790,7 +841,7 @@ static int krauler_command(const char *cmd, char *buf, size_t buflen)
}

/* Fabula communication subdriver */
static int fabula_command(const char *cmd, char *buf, size_t buflen)
static int _fabula_command(const char *cmd, char *buf, size_t buflen, char hunnox_patch)
{
const struct {
const char *str; /* Megatec command */
Expand Down Expand Up @@ -859,14 +910,59 @@ static int fabula_command(const char *cmd, char *buf, size_t buflen)

upsdebugx(4, "command index: 0x%02x", index);

if (hunnox_patch) {
// Enable lock-step protocol for Hunnox
if (hunnox_protocol(index) != 0) {
return 0;
}

// Seems that if we inform a large buffer, the USB locks.
// This value was captured from the Windows "official" client.
if (buflen > 102) {
buflen = 102;
}
}

/* Send command/Read reply */
ret = usb_get_string_simple(udev, index, buf, buflen);
if (langid_fix != -1) {
ret = usb_get_string(udev, index, langid_fix, buf, buflen);
} else {
ret = usb_get_string_simple(udev, index, buf, buflen);
}

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

if (hunnox_patch) {
if (langid_fix != -1) {
/* Limit this check, at least for now */
/* Invalid receive size - message corrupted */
if (ret != buf[0]) {
upsdebugx(1, "size mismatch: %d / %d", ret, buf[0]);
return 0;
}

/* Simple unicode -> ASCII inplace conversion
* FIXME: this code is at least shared with mge-shut/libshut
* Create a common function? */
unsigned int di, si, size = buf[0];
for (di = 0, si = 2; si < size; si += 2) {
if (di >= (buflen - 1))
break;

if (buf[si + 1]) /* high byte */
buf[di++] = '?';
else
buf[di++] = buf[si];
}

buf[di] = 0;
ret = di;
}
}

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

Expand All @@ -883,6 +979,16 @@ static int fabula_command(const char *cmd, char *buf, size_t buflen)
return ret;
}

static int fabula_command_hunnox(const char *cmd, char *buf, size_t buflen)
{
return _fabula_command(cmd, buf, buflen, TRUE);
}

static int fabula_command(const char *cmd, char *buf, size_t buflen)
{
return _fabula_command(cmd, buf, buflen, FALSE);
}

/* Fuji communication subdriver */
static int fuji_command(const char *cmd, char *buf, size_t buflen)
{
Expand Down Expand Up @@ -1043,6 +1149,12 @@ static void *fabula_subdriver(USBDevice_t *device)
return NULL;
}

static void *fabula_hunnox_subdriver(USBDevice_t *device)
{
subdriver_command = &fabula_command_hunnox;
return NULL;
}

static void *fuji_subdriver(USBDevice_t *device)
{
subdriver_command = &fuji_command;
Expand Down Expand Up @@ -1073,6 +1185,7 @@ static qx_usb_device_id_t qx_usb_id[] = {
{ USB_DEVICE(0x14f0, 0x00c9), NULL, NULL, &phoenix_subdriver }, /* GE EP series */
{ USB_DEVICE(0x0483, 0x0035), NULL, NULL, &sgs_subdriver }, /* TS Shara UPSes */
{ USB_DEVICE(0x0001, 0x0000), "MEC", "MEC0003", &fabula_subdriver }, /* Fideltronik/MEC LUPUS 500 USB */
{ USB_DEVICE(0x0001, 0x0000), NULL, "MEC0003", &fabula_hunnox_subdriver }, /* Hunnox HNX 850 */
{ USB_DEVICE(0x0001, 0x0000), "ATCL FOR UPS", "ATCL FOR UPS", &fuji_subdriver }, /* Fuji UPSes */
{ USB_DEVICE(0x0001, 0x0000), NULL, NULL, &krauler_subdriver }, /* Krauler UP-M500VA */
/* End of list */
Expand Down Expand Up @@ -1651,6 +1764,7 @@ void upsdrv_makevartable(void)
nut_usb_addvars();

addvar(VAR_VALUE, "langid_fix", "Apply the language ID workaround to the krauler subdriver (0x409 or 0x4095)");
addvar(VAR_FLAG, "noscanlangid", "Don't autoscan valid range for langid");
#endif /* QX_USB */

#ifdef QX_SERIAL
Expand Down Expand Up @@ -1923,6 +2037,7 @@ void upsdrv_initups(void)
{ "ippon", &ippon_command },
{ "krauler", &krauler_command },
{ "fabula", &fabula_command },
{ "fabula-hunnox", &fabula_command_hunnox },
{ "fuji", &fuji_command },
{ "sgs", &sgs_command },
{ NULL }
Expand Down Expand Up @@ -2017,7 +2132,7 @@ void upsdrv_initups(void)
dstate_setinfo("ups.productid", "%04x", usbdevice.ProductID);

/* Check for language ID workaround (#2) */
if (langid_fix != -1) {
if ((langid_fix != -1) && (!getval("noscanlangid"))) {
/* Future improvement:
* Asking for the zero'th index is special - it returns a string descriptor that contains all the language IDs supported by the device.
* Typically there aren't many - often only one.
Expand Down
137 changes: 137 additions & 0 deletions drivers/nutdrv_qx_hunnox.c
@@ -0,0 +1,137 @@
/* nutdrv_qx_zinto.c - Subdriver for Zinto protocol based UPSes
*
* Copyright (C)
* 2013 Daniele Pezzini <hyouko@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/

#include "main.h"
#include "nutdrv_qx.h"
#include "nutdrv_qx_blazer-common.h"

#include "nutdrv_qx_hunnox.h"

#define HUNNOX_VERSION "Hunnox 0.01"

/* qx2nut lookup table */
static item_t hunnox_qx2nut[] = {

/*
* > [Q1\r]
* < [(226.0 195.0 226.0 014 49.0 27.5 30.0 00001000\r]
* 01234567890123456789012345678901234567890123456
* 0 1 2 3 4
*/

{ "input.voltage", 0, NULL, "Q1\r", "", 47, '(', "", 1, 5, "%.1f", QX_FLAG_QUICK_POLL, NULL, NULL, NULL },
{ "input.voltage.fault", 0, NULL, "Q1\r", "", 47, '(', "", 7, 11, "%.1f", QX_FLAG_QUICK_POLL, NULL, NULL, NULL },
{ "output.voltage", 0, NULL, "Q1\r", "", 47, '(', "", 13, 17, "%.1f", QX_FLAG_QUICK_POLL, NULL, NULL, NULL },
{ "ups.load", 0, NULL, "Q1\r", "", 47, '(', "", 19, 21, "%.0f", QX_FLAG_QUICK_POLL, NULL, NULL, NULL },
{ "input.frequency", 0, NULL, "Q1\r", "", 47, '(', "", 23, 26, "%.1f", QX_FLAG_QUICK_POLL, NULL, NULL, NULL },
{ "battery.voltage", 0, NULL, "Q1\r", "", 47, '(', "", 28, 31, "%.2f", QX_FLAG_QUICK_POLL, NULL, NULL, NULL },
{ "ups.temperature", 0, NULL, "Q1\r", "", 47, '(', "", 33, 36, "%.1f", QX_FLAG_QUICK_POLL, NULL, NULL, NULL },
/* Status bits */
{ "ups.status", 0, NULL, "Q1\r", "", 47, '(', "", 38, 38, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, blazer_process_status_bits }, /* Utility Fail (Immediate) */
{ "ups.status", 0, NULL, "Q1\r", "", 47, '(', "", 39, 39, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, blazer_process_status_bits }, /* Battery Low */
{ "ups.status", 0, NULL, "Q1\r", "", 47, '(', "", 40, 40, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, blazer_process_status_bits }, /* Bypass/Boost or Buck Active */
{ "ups.alarm", 0, NULL, "Q1\r", "", 47, '(', "", 41, 41, NULL, 0, NULL, NULL, blazer_process_status_bits }, /* UPS Failed */
{ "ups.type", 0, NULL, "Q1\r", "", 47, '(', "", 42, 42, "%s", QX_FLAG_STATIC, NULL, NULL, blazer_process_status_bits }, /* UPS Type */
{ "ups.status", 0, NULL, "Q1\r", "", 47, '(', "", 43, 43, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, blazer_process_status_bits }, /* Test in Progress */
{ "ups.status", 0, NULL, "Q1\r", "", 47, '(', "", 44, 44, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, blazer_process_status_bits }, /* Shutdown Active */
{ "ups.beeper.status", 0, NULL, "Q1\r", "", 47, '(', "", 45, 45, "%s", 0, NULL, NULL, blazer_process_status_bits }, /* Beeper status */

/*
* > [F\r]
* < [#220.0 000 024.0 50.0\r]
* 0123456789012345678901
* 0 1 2
*/

{ "input.voltage.nominal", 0, NULL, "F\r", "", 22, '#', "", 1, 5, "%.0f", QX_FLAG_STATIC, NULL, NULL, NULL },
{ "input.current.nominal", 0, NULL, "F\r", "", 22, '#', "", 7, 9, "%.1f", QX_FLAG_STATIC, NULL, NULL, NULL },
{ "battery.voltage.nominal", 0, NULL, "F\r", "", 22, '#', "", 11, 15, "%.1f", QX_FLAG_STATIC, NULL, NULL, NULL },
{ "input.frequency.nominal", 0, NULL, "F\r", "", 22, '#', "", 17, 20, "%.0f", QX_FLAG_STATIC, NULL, NULL, NULL },
/*
* > [FW?\r]
* < [#------------- ------ VT12046Q \r]
* 012345678901234567890123456789012345678
* 0 1 2 3
*/

{ "device.mfr", 0, NULL, "FW?\r", "", 39, '#', "", 1, 15, "%s", QX_FLAG_STATIC | QX_FLAG_TRIM, NULL, NULL, NULL },
{ "device.model", 0, NULL, "FW?\r", "", 39, '#', "", 17, 26, "%s", QX_FLAG_STATIC | QX_FLAG_TRIM, NULL, NULL, NULL },
{ "ups.firmware", 0, NULL, "FW?\r", "", 39, '#', "", 28, 37, "%s", QX_FLAG_STATIC | QX_FLAG_TRIM, NULL, NULL, NULL },

/* Instant commands */
{ "beeper.toggle", 0, NULL, "Q\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL },
{ "load.off", 0, NULL, "S00R0000\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL },
{ "load.on", 0, NULL, "C\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL },
{ "shutdown.return", 0, NULL, "S%s\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, blazer_process_command },
{ "shutdown.stayoff", 0, NULL, "S%sR0000\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, blazer_process_command },
{ "shutdown.stop", 0, NULL, "C\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL },
{ "test.battery.start", 0, NULL, "T%02d\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, blazer_process_command },
{ "test.battery.start.deep", 0, NULL, "TL\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL },
{ "test.battery.start.quick", 0, NULL, "T\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL },
{ "test.battery.stop", 0, NULL, "CT\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL },

/* Server-side settable vars */
{ "ups.delay.start", ST_FLAG_RW, blazer_r_ondelay, NULL, "", 0, 0, "", 0, 0, "0", QX_FLAG_ABSENT | QX_FLAG_SETVAR | QX_FLAG_RANGE, NULL, NULL, blazer_process_setvar },
{ "ups.delay.shutdown", ST_FLAG_RW, blazer_r_offdelay, NULL, "", 0, 0, "", 0, 0, "60", QX_FLAG_ABSENT | QX_FLAG_SETVAR | QX_FLAG_RANGE, NULL, NULL, blazer_process_setvar },

/* End of structure. */
{ NULL, 0, NULL, NULL, "", 0, 0, "", 0, 0, NULL, 0, NULL, NULL, NULL }
};

/* Testing table */
#ifdef TESTING
static testing_t hunnox_testing[] = {
{ "Q1\r", "(215.0 195.0 230.0 014 49.0 22.7 30.0 00000000\r", -1 },
{ "F\r", "#230.0 000 024.0 50.0\r", -1 },
{ "FW?\r", "#NOT_A_LIVE_UPS TESTING TESTING \r", -1 },
{ "Q\r", "", -1 },
{ "S03\r", "", -1 },
{ "C\r", "", -1 },
{ "S02R0005\r", "", -1 },
{ "S.5R0000\r", "", -1 },
{ "T04\r", "", -1 },
{ "TL\r", "", -1 },
{ "T\r", "", -1 },
{ "CT\r", "", -1 },
{ NULL }
};
#endif /* TESTING */

/* Subdriver-specific initups */
static void hunnox_initups(void)
{
blazer_initups(hunnox_qx2nut);
}

/* Subdriver interface */
subdriver_t hunnox_subdriver = {
HUNNOX_VERSION,
blazer_claim,
hunnox_qx2nut,
hunnox_initups,
NULL,
blazer_makevartable,
"UPS No Ack",
NULL,
#ifdef TESTING
hunnox_testing,
#endif /* TESTING */
};
29 changes: 29 additions & 0 deletions drivers/nutdrv_qx_hunnox.h
@@ -0,0 +1,29 @@
/* nutdrv_qx_zinto.h - Subdriver for Zinto protocol based UPSes
*
* Copyright (C)
* 2013 Daniele Pezzini <hyouko@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/

#ifndef NUTDRV_QX_HUNNOX_H
#define NUTDRV_QX_HUNNOX_H

#include "nutdrv_qx.h"

extern subdriver_t hunnox_subdriver;

#endif /* NUTDRV_QX_HUNNOX_H */

0 comments on commit d8eaf12

Please sign in to comment.