From d8eaf12d5aeb1e0845aa6010d0df05485844365c Mon Sep 17 00:00:00 2001 From: mjan Date: Sat, 1 Dec 2018 17:24:07 -0300 Subject: [PATCH] Add support for Hunnox HNX-850 --- drivers/Makefile.am | 3 +- drivers/nutdrv_qx.c | 121 +++++++++++++++++++++++++++++++- drivers/nutdrv_qx_hunnox.c | 137 +++++++++++++++++++++++++++++++++++++ drivers/nutdrv_qx_hunnox.h | 29 ++++++++ 4 files changed, 286 insertions(+), 4 deletions(-) create mode 100644 drivers/nutdrv_qx_hunnox.c create mode 100644 drivers/nutdrv_qx_hunnox.h diff --git a/drivers/Makefile.am b/drivers/Makefile.am index 20d006eb71..92d42baccb 100644 --- a/drivers/Makefile.am +++ b/drivers/Makefile.am @@ -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) # ---------------------------------------------------------------------- diff --git a/drivers/nutdrv_qx.c b/drivers/nutdrv_qx.c index bb526608c7..743d65fb91 100644 --- a/drivers/nutdrv_qx.c +++ b/drivers/nutdrv_qx.c @@ -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" @@ -84,6 +85,7 @@ static subdriver_t *subdriver_list[] = { &mecer_subdriver, &megatec_subdriver, &zinto_subdriver, + &hunnox_subdriver, /* Fallback Q1 subdriver */ &q1_subdriver, NULL @@ -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 */ @@ -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) { @@ -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 */ @@ -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); @@ -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) { @@ -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; @@ -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 */ @@ -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 @@ -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 } @@ -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. diff --git a/drivers/nutdrv_qx_hunnox.c b/drivers/nutdrv_qx_hunnox.c new file mode 100644 index 0000000000..b5297fbcb7 --- /dev/null +++ b/drivers/nutdrv_qx_hunnox.c @@ -0,0 +1,137 @@ +/* nutdrv_qx_zinto.c - Subdriver for Zinto protocol based UPSes + * + * Copyright (C) + * 2013 Daniele Pezzini + * + * 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 */ +}; diff --git a/drivers/nutdrv_qx_hunnox.h b/drivers/nutdrv_qx_hunnox.h new file mode 100644 index 0000000000..11dfd47db1 --- /dev/null +++ b/drivers/nutdrv_qx_hunnox.h @@ -0,0 +1,29 @@ +/* nutdrv_qx_zinto.h - Subdriver for Zinto protocol based UPSes + * + * Copyright (C) + * 2013 Daniele Pezzini + * + * 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 */