From b36e89b01714c59ef471745c163a86e57c3da254 Mon Sep 17 00:00:00 2001 From: Viktor Drobot Date: Sun, 9 Feb 2025 13:40:14 +0300 Subject: [PATCH 01/10] Add basic Q2 and Q6 protocol variants. Improve INNOVART31 protocol --- docs/man/nutdrv_qx.txt | 25 ++- docs/nut.dict | 1 - docs/nutdrv_qx-subdrivers.txt | 2 + drivers/Makefile.am | 4 +- drivers/nutdrv_qx.c | 17 ++ drivers/nutdrv_qx.h | 3 + drivers/nutdrv_qx_blazer-common.c | 2 +- drivers/nutdrv_qx_blazer-common.h | 2 +- drivers/nutdrv_qx_innovart31.c | 82 ++++--- drivers/nutdrv_qx_q2.c | 341 +++++++++++++++++++++++++++++ drivers/nutdrv_qx_q2.h | 29 +++ drivers/nutdrv_qx_q6.c | 342 ++++++++++++++++++++++++++++++ drivers/nutdrv_qx_q6.h | 29 +++ 13 files changed, 839 insertions(+), 40 deletions(-) create mode 100644 drivers/nutdrv_qx_q2.c create mode 100644 drivers/nutdrv_qx_q2.h create mode 100644 drivers/nutdrv_qx_q6.c create mode 100644 drivers/nutdrv_qx_q6.h diff --git a/docs/man/nutdrv_qx.txt b/docs/man/nutdrv_qx.txt index b91eca2497..03f3d62aab 100644 --- a/docs/man/nutdrv_qx.txt +++ b/docs/man/nutdrv_qx.txt @@ -67,7 +67,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', 'voltronic', 'voltronic-qs', 'voltronic-qs-hex' and 'zinto'. +Supported values: 'bestups', '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. @@ -147,8 +147,8 @@ If not specified, the driver defaults to 10%. Only used if *runtimecal* is also specified. -BESTUPS, INNOVART31, MECER, MEGATEC, MEGATEC/OLD, MUSTEK, Q1, VOLTRONIC-QS, VOLTRONIC-QS-HEX, ZINTO PROTOCOLS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +BESTUPS, INNOVART31, MECER, MEGATEC, MEGATEC/OLD, MUSTEK, Q1, Q2, Q6, VOLTRONIC-QS, VOLTRONIC-QS-HEX, ZINTO PROTOCOLS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *ignoresab*:: Some UPSes incorrectly report the `Shutdown Active' bit as always on, consequently making the driver believe the UPS is nearing a shutdown (and, as a result, ups.status always contains +FSD+... and you know what this means). @@ -156,8 +156,8 @@ Setting this flag will make the driver ignore the `Shutdown Active' bit. [[old-blazer-protocols-options]] -MECER, MEGATAEC, MEGATEC/OLD, MUSTEK, ZINTO PROTOCOLS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +MECER, MEGATEC, MEGATEC/OLD, MUSTEK, ZINTO PROTOCOLS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *ondelay*:: The acceptable range is +0..599940+ seconds. @@ -196,8 +196,8 @@ Safeguard against talking to the wrong one of several identical UPSes on the sam Note that when changing *ups.id* (through linkman:upsrw[8]) the driver will continue to talk to the UPS with the new 'slave address', but won't claim it again on restart until the *slave_addr* parameter is adjusted. -INNOVART31, Q1 PROTOCOLS -~~~~~~~~~~~~~~~~~~~~~~~~ +INNOVART31, Q1, Q2, Q6 PROTOCOLS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *ondelay*:: The acceptable range is +0..599940+ seconds. @@ -206,6 +206,13 @@ The acceptable range is +0..599940+ seconds. The acceptable range is +12..600+ seconds. +Q2, Q6 PROTOCOLS +~~~~~~~~~~~~~~~~ + +*nooutstats*:: +Some UPSes doesn't support `WA' command which returns output load stats. Using this flag will make the driver ignore these requests. + + VOLTRONIC-QS, VOLTRONIC-QS-HEX PROTOCOLS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -439,8 +446,8 @@ Stop a running battery test. (Not available on some hardware) -BESTUPS, INNOVART31, MECER, MEGATEC, MEGATEC/OLD, MUSTEK, Q1, ZINTO PROTOCOLS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +BESTUPS, INNOVART31, MECER, MEGATEC, MEGATEC/OLD, MUSTEK, Q1, Q2, Q6, ZINTO PROTOCOLS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *test.battery.start* 'value':: Perform a battery test for the duration of 'value' seconds (truncated to 60 seconds) [+60..5940+]. diff --git a/docs/nut.dict b/docs/nut.dict index 1cc4c8f6cf..1d7495ed7d 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -646,7 +646,6 @@ MBATTCHG MCU MDigest MEC -MEGATAEC MERCHANTABILITY MF MH diff --git a/docs/nutdrv_qx-subdrivers.txt b/docs/nutdrv_qx-subdrivers.txt index dfabbc8f55..a3a5bfb730 100644 --- a/docs/nutdrv_qx-subdrivers.txt +++ b/docs/nutdrv_qx-subdrivers.txt @@ -1178,6 +1178,8 @@ For more details, have a look at the currently available subdrivers: - +nutdrv_qx_megatec-old.+{+c+,+h+} - +nutdrv_qx_mustek.+{+c+,+h+} - +nutdrv_qx_q1.+{+c+,+h+} +- +nutdrv_qx_q2.+{+c+,+h+} +- +nutdrv_qx_q6.+{+c+,+h+} - +nutdrv_qx_voltronic.+{+c+,+h+} - +nutdrv_qx_voltronic-qs.+{+c+,+h+} - +nutdrv_qx_voltronic-qs-hex.+{+c+,+h+} diff --git a/drivers/Makefile.am b/drivers/Makefile.am index 4ea7ab4a44..448fb64821 100644 --- a/drivers/Makefile.am +++ b/drivers/Makefile.am @@ -382,7 +382,7 @@ NUTDRV_QX_SUBDRIVERS = nutdrv_qx_bestups.c nutdrv_qx_blazer-common.c \ nutdrv_qx_innovart31.c \ nutdrv_qx_masterguard.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_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_SOURCES += $(NUTDRV_QX_SUBDRIVERS) @@ -408,7 +408,7 @@ dist_noinst_HEADERS = \ 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_hunnox.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 \ xppc-mib.h huawei-mib.h eaton-ats16-nmc-mib.h eaton-ats16-nm2-mib.h apc-ats-mib.h raritan-px2-mib.h eaton-ats30-mib.h \ diff --git a/drivers/nutdrv_qx.c b/drivers/nutdrv_qx.c index e7874d8e28..a14fe3ae93 100644 --- a/drivers/nutdrv_qx.c +++ b/drivers/nutdrv_qx.c @@ -77,6 +77,8 @@ #include "nutdrv_qx_megatec-old.h" #include "nutdrv_qx_mustek.h" #include "nutdrv_qx_q1.h" +#include "nutdrv_qx_q2.h" +#include "nutdrv_qx_q6.h" #include "nutdrv_qx_voltronic.h" #include "nutdrv_qx_voltronic-qs.h" #include "nutdrv_qx_voltronic-qs-hex.h" @@ -99,6 +101,8 @@ static subdriver_t *subdriver_list[] = { &hunnox_subdriver, &ablerex_subdriver, &innovart31_subdriver, + &q2_subdriver, + &q6_subdriver, /* Fallback Q1 subdriver */ &q1_subdriver, NULL @@ -258,6 +262,19 @@ int qx_multiply_battvolt(item_t *item, char *value, const size_t valuelen) { return 0; } +/* Convert kilo-values to their full representation */ +int qx_multiply_x1000(item_t *item, char *value, const size_t valuelen) { + float s = 0; + + if (sscanf(item->value, "%f", &s) != 1) { + upsdebugx(2, "unparsable ss.ss %s", item->value); + return -1; + } + + snprintf(value, valuelen, "%.2f", s * 1000.0); + return 0; +} + /* Fill batt.volt.act and guesstimate the battery charge * if it isn't already available. */ static int qx_battery(void) diff --git a/drivers/nutdrv_qx.h b/drivers/nutdrv_qx.h index faffe717cc..74af864cdf 100644 --- a/drivers/nutdrv_qx.h +++ b/drivers/nutdrv_qx.h @@ -189,6 +189,9 @@ void update_status(const char *nutvalue); /* Let subdrivers reference this: for devices that report "battery.voltage" of a single cell/pack, optionally multiply that into representing the whole assembly */ int qx_multiply_battvolt(item_t *item, char *value, const size_t valuelen); + /* Convert kilo-values to their full representation */ +int qx_multiply_x1000(item_t *item, char *value, const size_t valuelen); + /* Data for processing status values */ #define STATUS(x) ((unsigned int)1U< diff --git a/drivers/nutdrv_qx_blazer-common.h b/drivers/nutdrv_qx_blazer-common.h index f8344b9404..b79edbd990 100644 --- a/drivers/nutdrv_qx_blazer-common.h +++ b/drivers/nutdrv_qx_blazer-common.h @@ -1,4 +1,4 @@ -/* nutdrv_qx_blazer-common.h - Common functions/settings for nutdrv_qx_{innovart31,mecer,megatec,megatec-old,mustek,q1,voltronic-qs,zinto}.{c,h} +/* nutdrv_qx_blazer-common.h - Common functions/settings for nutdrv_qx_{innovart31,mecer,megatec,megatec-old,mustek,q1,q2,q6,voltronic-qs,zinto}.{c,h} * * Copyright (C) * 2013 Daniele Pezzini diff --git a/drivers/nutdrv_qx_innovart31.c b/drivers/nutdrv_qx_innovart31.c index 794106ad47..39ba848650 100644 --- a/drivers/nutdrv_qx_innovart31.c +++ b/drivers/nutdrv_qx_innovart31.c @@ -25,20 +25,13 @@ #include "nutdrv_qx_innovart31.h" -#define INNOVART31_VERSION "INNOVART31 0.01" +#define INNOVART31_VERSION "INNOVART31 0.02" -/* Internal function: used to convert kilo-values to their full representation */ -static int innovart31_x1000(item_t *item, char *value, const size_t valuelen) { - float s = 0; +/* Support functions */ +static int innovart31_claim(void); +static void innovart31_initups(void); +static void innovart31_initinfo(void); - if (sscanf(item->value, "%f", &s) != 1) { - upsdebugx(2, "unparsable ss.ss %s", item->value); - return -1; - } - - snprintf(value, valuelen, "%.2f", s * 1000.0); - return 0; -} /* qx2nut lookup table */ static item_t innovart31_qx2nut[] = { @@ -50,19 +43,15 @@ static item_t innovart31_qx2nut[] = { */ /* Common parameters */ - { "input.voltage", 0, NULL, "Q6\r", "", 110, '(', "", 1, 5, "%.1f", 0, NULL, NULL, NULL }, { "input.L1-N.voltage", 0, NULL, "Q6\r", "", 110, '(', "", 1, 5, "%.1f", 0, NULL, NULL, NULL }, { "input.L2-N.voltage", 0, NULL, "Q6\r", "", 110, '(', "", 7, 11, "%.1f", 0, NULL, NULL, NULL }, { "input.L3-N.voltage", 0, NULL, "Q6\r", "", 110, '(', "", 13, 17, "%.1f", 0, NULL, NULL, NULL }, { "input.frequency", 0, NULL, "Q6\r", "", 110, '(', "", 19, 22, "%.1f", 0, NULL, NULL, NULL }, { "output.voltage", 0, NULL, "Q6\r", "", 110, '(', "", 24, 28, "%.1f", 0, NULL, NULL, NULL }, -/* { "output.L1-N.voltage", 0, NULL, "Q6\r", "", 110, '(', "", 24, 28, "%.1f", 0, NULL, NULL, NULL }, - { "output.L2-N.voltage", 0, NULL, "Q6\r", "", 110, '(', "", 30, 34, "%.1f", 0, NULL, NULL, NULL }, - { "output.L3-N.voltage", 0, NULL, "Q6\r", "", 110, '(', "", 36, 40, "%.1f", 0, NULL, NULL, NULL },*/ { "output.frequency", 0, NULL, "Q6\r", "", 110, '(', "", 42, 45, "%.1f", 0, NULL, NULL, NULL }, { "ups.load", 0, NULL, "Q6\r", "", 110, '(', "", 47, 49, "%.0f", 0, NULL, NULL, NULL }, { "ups.temperature", 0, NULL, "Q6\r", "", 110, '(', "", 71, 74, "%.1f", 0, NULL, NULL, NULL }, - { "battery.voltage", 0, NULL, "Q6\r", "", 110, '(', "", 59, 63, "%.2f", 0, NULL, NULL, qx_multiply_battvolt }, + { "battery.voltage", 0, NULL, "Q6\r", "", 110, '(', "", 59, 63, "%.1f", 0, NULL, NULL, qx_multiply_battvolt }, { "battery.runtime", 0, NULL, "Q6\r", "", 110, '(', "", 76, 80, "%.0f", 0, NULL, NULL, NULL }, { "battery.charge", 0, NULL, "Q6\r", "", 110, '(', "", 82, 84, "%.0f", 0, NULL, NULL, NULL }, @@ -75,8 +64,8 @@ static item_t innovart31_qx2nut[] = { /* Output consumption parameters */ { "output.current", 0, NULL, "WA\r", "", 80, '(', "", 49, 53, "%.1f", 0, NULL, NULL, NULL }, - { "ups.realpower", 0, NULL, "WA\r", "", 80, '(', "", 37, 41, "%.1f", 0, NULL, NULL, innovart31_x1000 }, - { "ups.power", 0, NULL, "WA\r", "", 80, '(', "", 43, 47, "%.1f", 0, NULL, NULL, innovart31_x1000 }, + { "ups.realpower", 0, NULL, "WA\r", "", 80, '(', "", 37, 41, "%.1f", 0, NULL, NULL, qx_multiply_x1000 }, + { "ups.power", 0, NULL, "WA\r", "", 80, '(', "", 43, 47, "%.1f", 0, NULL, NULL, qx_multiply_x1000 }, /* * > [BPS\r] @@ -87,9 +76,6 @@ static item_t innovart31_qx2nut[] = { /* Bypass parameters */ { "input.bypass.voltage", 0, NULL, "BPS\r", "", 24, '(', "", 1, 5, "%.1f", 0, NULL, NULL, NULL }, -/* { "input.bypass.L1-N.voltage", 0, NULL, "BPS\r", "", 24, '(', "", 1, 5, "%.1f", 0, NULL, NULL, NULL }, - { "input.bypass.L2-N.voltage", 0, NULL, "BPS\r", "", 24, '(', "", 7, 11, "%.1f", 0, NULL, NULL, NULL }, - { "input.bypass.L3-N.voltage", 0, NULL, "BPS\r", "", 24, '(', "", 13, 17, "%.1f", 0, NULL, NULL, NULL },*/ { "input.bypass.frequency", 0, NULL, "BPS\r", "", 24, '(', "", 19, 22, "%.1f", 0, NULL, NULL, NULL }, /* @@ -165,8 +151,8 @@ static item_t innovart31_qx2nut[] = { /* Testing table */ #ifdef TESTING static testing_t innovart31_testing[] = { - { "Q1\r", "(215.0 195.0 230.0 014 49.0 22.7 30.0 00000000\r", -1 }, { "Q6\r", "(227.0 225.6 230.0 50.0 229.9 000.0 000.0 49.9 007 000 000 327.8 000.0 23.0 06932 100 32 00000000 00000000 11\r", -1 }, + { "Q1\r", "(215.0 195.0 230.0 014 49.0 22.7 30.0 00000000\r", -1 }, { "WA\r", "(001.4 000.0 000.0 001.4 000.0 000.0 001.4 001.4 006.5 000.0 000.0 007 00000000\r", -1 }, { "BPS\r", "(230.4 000.0 000.0 49.9\r", -1 }, { "F\r", "#230.0 087 288.0 50.0\r", -1 }, @@ -184,10 +170,54 @@ static testing_t innovart31_testing[] = { }; #endif /* TESTING */ +/* == Support functions == */ + +/* This function allows the subdriver to "claim" a device: return 1 if the device is supported by this subdriver, else 0 */ +static int innovart31_claim(void) +{ + /* We need Q6, Q1, WA, BPS, F, FW? and SASV07? to use this subdriver */ + struct { + char *var; + char *cmd; + } mandatory[] = { + { "input.L1-N.voltage", "Q6" }, + { "ups.type", "Q1" }, + { "output.current", "WA" }, + { "input.bypass.voltage", "BPS" }, + { "input.voltage.nominal", "F" }, + { "ups.firmware", "FW?" }, + { "ups.serial", "SASV07?" }, + { NULL, NULL} + }; + int vari; + char *sp, *cmd; + + for (vari = 0; mandatory[vari].var; vari++) { + sp = mandatory[vari].var; + cmd = mandatory[vari].cmd; + + item_t *item = find_nut_info(sp, 0, 0); + + /* Don't know what happened */ + if (!item) + return 0; + + /* No reply/Unable to get value */ + if (qx_process(item, NULL)) + return 0; + + /* Unable to process value */ + if (ups_infoval_set(item) != 1) + return 0; + } + + return 1; +} + /* Subdriver-specific initups */ static void innovart31_initups(void) { - blazer_initups(innovart31_qx2nut); + blazer_initups_light(innovart31_qx2nut); } /* Subdriver-specific initinfo */ @@ -201,11 +231,11 @@ static void innovart31_initinfo(void) /* Subdriver interface */ subdriver_t innovart31_subdriver = { INNOVART31_VERSION, - blazer_claim, + innovart31_claim, innovart31_qx2nut, innovart31_initups, innovart31_initinfo, - blazer_makevartable, + blazer_makevartable_light, "ACK", "NAK\r", #ifdef TESTING diff --git a/drivers/nutdrv_qx_q2.c b/drivers/nutdrv_qx_q2.c new file mode 100644 index 0000000000..4549491a34 --- /dev/null +++ b/drivers/nutdrv_qx_q2.c @@ -0,0 +1,341 @@ +/* nutdrv_qx_q2.c - Subdriver for Q2 Megatec protocol variant + * + * Copyright (C) + * 2024 Viktor Drobot + * + * 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 "nut_float.h" /* for fabs() */ +#include "nutdrv_qx.h" +#include "nutdrv_qx_blazer-common.h" + +#include "nutdrv_qx_q2.h" + +#define Q2_VERSION "Q2 0.01" + +/* Support functions */ +static int q2_process_topology_bits(item_t *item, char *value, const size_t valuelen); +static int q2_claim(void); +static void q2_initups(void); +static void q2_initinfo(void); +static void q2_makevartable(void); + +/* qx2nut lookup table */ +static item_t q2_qx2nut[] = { + + /* + * > [Q2\r] + * < [(MMM.M MMM.M MMM.M NNN.N PPP.P PPP.P PPP.P QQQ QQQ QQQ RR.R SSS.S TT.T b7b6b5b4b3b2b1b0 ttt.tt CCC BB ff ff ff ff wwwwwwww YO\r] + * 012345678901234567890123456789012345678901234567890123456789012345678901 2 3 4 5 6 7 8 901234567890123456789012345678901234567 + * 0 1 2 3 4 5 6 7 8 9 10 11 + */ + + /* Common parameters */ + { "input.L1-N.voltage", 0, NULL, "Q2\r", "", 118, '(', "", 1, 5, "%.1f", 0, NULL, NULL, NULL }, + { "input.L2-N.voltage", 0, NULL, "Q2\r", "", 118, '(', "", 7, 11, "%.1f", 0, NULL, NULL, NULL }, + { "input.L3-N.voltage", 0, NULL, "Q2\r", "", 118, '(', "", 13, 17, "%.1f", 0, NULL, NULL, NULL }, + { "input.voltage.fault", 0, NULL, "Q2\r", "", 118, '(', "", 19, 23, "%.1f", 0, NULL, NULL, NULL }, + { "input.frequency", 0, NULL, "Q2\r", "", 118, '(', "", 55, 58, "%.1f", 0, NULL, NULL, NULL }, + { "output.L1-N.voltage", 0, NULL, "Q2\r", "", 118, '(', "", 25, 29, "%.1f", 0, NULL, NULL, NULL }, + { "output.L2-N.voltage", 0, NULL, "Q2\r", "", 118, '(', "", 31, 35, "%.1f", 0, NULL, NULL, NULL }, + { "output.L3-N.voltage", 0, NULL, "Q2\r", "", 118, '(', "", 37, 41, "%.1f", 0, NULL, NULL, NULL }, + { "output.L1.load", 0, NULL, "Q2\r", "", 118, '(', "", 43, 45, "%.0f", 0, NULL, NULL, NULL }, + { "output.L2.load", 0, NULL, "Q2\r", "", 118, '(', "", 47, 49, "%.0f", 0, NULL, NULL, NULL }, + { "output.L3.load", 0, NULL, "Q2\r", "", 118, '(', "", 51, 53, "%.0f", 0, NULL, NULL, NULL }, + { "ups.temperature", 0, NULL, "Q2\r", "", 118, '(', "", 66, 69, "%.1f", 0, NULL, NULL, NULL }, + { "battery.voltage", 0, NULL, "Q2\r", "", 118, '(', "", 60, 64, "%.1f", 0, NULL, NULL, qx_multiply_battvolt }, + { "battery.runtime", 0, NULL, "Q2\r", "", 118, '(', "", 80, 85, "%.0f", 0, NULL, NULL, NULL }, + { "battery.charge", 0, NULL, "Q2\r", "", 118, '(', "", 87, 89, "%.0f", 0, NULL, NULL, NULL }, + { "experimental.input.topology", 0, NULL, "Q2\r", "", 118, '(', "", 115, 115, "%s", QX_FLAG_STATIC, NULL, NULL, q2_process_topology_bits }, /* Input transformer topology */ + { "experimental.output.topology", 0, NULL, "Q2\r", "", 118, '(', "", 116, 116, "%s", QX_FLAG_STATIC, NULL, NULL, q2_process_topology_bits }, /* Output transformer topology */ + /* TODO handle BB (91-92) properly: mb use "experimental." vars for that */ + + /* + * > [WA\r] + * < [(WWW.W WWW.W WWW.W VVV.V VVV.V VVV.V TTT.T SSS.S AAA.A AAA.A AAA.A QQQ b7b6b5b4b3b2b1b0\r] + * 012345678901234567890123456789012345678901234567890123456789012345678901 2 3 4 5 6 7 8 9 + * 0 1 2 3 4 5 6 7 + */ + + /* Output consumption parameters */ + { "output.L1.realpower", 0, NULL, "WA\r", "", 80, '(', "", 1, 5, "%.1f", 0, NULL, NULL, qx_multiply_x1000 }, + { "output.L2.realpower", 0, NULL, "WA\r", "", 80, '(', "", 7, 11, "%.1f", 0, NULL, NULL, qx_multiply_x1000 }, + { "output.L3.realpower", 0, NULL, "WA\r", "", 80, '(', "", 13, 17, "%.1f", 0, NULL, NULL, qx_multiply_x1000 }, + { "output.L1.power", 0, NULL, "WA\r", "", 80, '(', "", 19, 23, "%.1f", 0, NULL, NULL, qx_multiply_x1000 }, + { "output.L2.power", 0, NULL, "WA\r", "", 80, '(', "", 25, 29, "%.1f", 0, NULL, NULL, qx_multiply_x1000 }, + { "output.L3.power", 0, NULL, "WA\r", "", 80, '(', "", 31, 35, "%.1f", 0, NULL, NULL, qx_multiply_x1000 }, + { "output.L1.current", 0, NULL, "WA\r", "", 80, '(', "", 49, 53, "%.1f", 0, NULL, NULL, NULL }, + { "output.L2.current", 0, NULL, "WA\r", "", 80, '(', "", 55, 59, "%.1f", 0, NULL, NULL, NULL }, + { "output.L3.current", 0, NULL, "WA\r", "", 80, '(', "", 61, 65, "%.1f", 0, NULL, NULL, NULL }, + { "ups.realpower", 0, NULL, "WA\r", "", 80, '(', "", 37, 41, "%.1f", 0, NULL, NULL, qx_multiply_x1000 }, + { "ups.power", 0, NULL, "WA\r", "", 80, '(', "", 43, 47, "%.1f", 0, NULL, NULL, qx_multiply_x1000 }, + { "ups.load", 0, NULL, "WA\r", "", 80, '(', "", 67, 69, "%.0f", 0, NULL, NULL, NULL }, + + /* + * > [Q1\r] + * < [(MMM.M NNN.N PPP.P QQQ RR.R S.SS TT.T b7b6b5b4b3b2b1b0\r] + * 012345678901234567890123456789012345678 9 0 1 2 3 4 5 6 + * 0 1 2 3 4 + */ + + /* 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 */ + + /* 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, DEFAULT_ONDELAY, 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, DEFAULT_OFFDELAY, 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 q2_testing[] = { + { "Q2\r", "(226.6 224.3 228.0 226.6 229.9 000.0 000.0 000 000 000 49.9 327.6 18.0 00000000 999.24 100 02 00 00 00 00 00000000 11\r", -1 }, + { "WA\r", "(001.4 000.0 000.0 001.4 000.0 000.0 001.4 001.4 006.5 000.0 000.0 007 00000000\r", -1 }, + { "Q1\r", "(215.0 195.0 230.0 014 49.0 22.7 30.0 00000000\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 */ + +/* == Support functions == */ + +/* This function allows to process input/output transformers topology bits */ +static int q2_process_topology_bits(item_t *item, char *value, const size_t valuelen) +{ + char *val = ""; + + if (strspn(item->value, "01") != 1) { + upsdebugx(3, "%s: unexpected value %s@%d->%s", __func__, item->value, item->from, item->value); + return -1; + } + + switch (item->from) + { + case 115: /* Input topology - experimental.input.topology */ + + if (item->value[0] == '1') + val = "star"; + else if (item->value[0] == '0') + val = "delta"; + else + val = "unknown"; + break; + + case 116: /* Output topology - experimental.output.topology */ + + if (item->value[0] == '1') + val = "star"; + else if (item->value[0] == '0') + val = "delta"; + else + val = "unknown"; + break; + + default: + /* Don't know what happened */ + return -1; + } + + snprintf(value, valuelen, "%s", val); + + return 0; +} + +/* This function allows the subdriver to "claim" a device: return 1 if the device is supported by this subdriver, else 0 */ +static int q2_claim(void) +{ + /* We need Q2, Q1, and WA to use this subdriver (only if user hasn't requested 'nooutstats' flag */ + struct { + char *var; + char *cmd; + } mandatory[] = { + { "input.L1-N.voltage", "Q2" }, + { "ups.type", "Q1" }, + { "output.L1.current", "WA"}, + { NULL, NULL } + }; + int vari; + char *sp, *cmd; + + for (vari = 0; mandatory[vari].var; vari++) { + sp = mandatory[vari].var; + cmd = mandatory[vari].cmd; + + /* Ignore checks for 'WA' command support if explicitly asked */ + if (testvar("nooutstats") && (strcasecmp(cmd, "WA") == 0)) + continue; + + item_t *item = find_nut_info(sp, 0, 0); + + /* Don't know what happened */ + if (!item) + return 0; + + /* No reply/Unable to get value */ + if (qx_process(item, NULL)) + return 0; + + /* Unable to process value */ + if (ups_infoval_set(item) != 1) + return 0; + } + + return 1; +} + +/* Subdriver-specific initups */ +static void q2_initups(void) +{ + int nr, nos, isb; + item_t *item; + + nr = testvar("norating"); + nos = testvar("nooutstats"); + isb = testvar("ignoresab"); + + if (!nr && !nos && !isb) + return; + + for (item = q2_qx2nut; item->info_type != NULL; item++) { + + if (!item->command) + continue; + + /* norating */ + if (nr && (strcasecmp(item->command, "F\r") == 0)) { + upsdebugx(2, "%s: skipping %s", __func__, item->info_type); + item->qxflags |= QX_FLAG_SKIP; + } + + /* nooutstats */ + if (nos && (strcasecmp(item->command, "WA\r") == 0)) { + upsdebugx(2, "%s: skipping %s", __func__, item->info_type); + item->qxflags |= QX_FLAG_SKIP; + } + + /* ignoresab */ + if (isb && (strcasecmp(item->info_type, "ups.status") == 0) && item->from == 44 && item->to == 44) { + upsdebugx(2, "%s: skipping %s ('Shutdown Active' bit)", __func__, item->info_type); + item->qxflags |= QX_FLAG_SKIP; + } + } +} + +/* Subdriver-specific initinfo */ +static void q2_initinfo(void) +{ + dstate_setinfo("input.phases", "%u", 3); + dstate_setinfo("output.phases", "%u", 3); +} + +/* Subdriver-specific flags/vars */ +static void q2_makevartable(void) +{ + addvar(VAR_FLAG, "norating", "Skip reading rating information from UPS"); + addvar(VAR_FLAG, "nooutstats", "Skip reading output load stats information from UPS"); + + blazer_makevartable_light(); +} + +void upsdrv_updateinfo(void) { + const char *i_L1N = dstate_getinfo("input.L1-N.voltage"); + const char *i_L2N = dstate_getinfo("input.L2-N.voltage"); + const char *i_L3N = dstate_getinfo("input.L3-N.voltage"); + const char *o_L1N = dstate_getinfo("output.L1-N.voltage"); + const char *o_L2N = dstate_getinfo("output.L2-N.voltage"); + const char *o_L3N = dstate_getinfo("output.L3-N.voltage"); + const double thr = 10.0; /* threshold for comparison; mb better to use user-defined variable */ + + unsigned int inphases = 0, outphases = 0; + double L1N, L2N, L3N; + + if (i_L1N && i_L2N && i_L3N) { + L1N = strtod(i_L1N, NULL); + L2N = strtod(i_L2N, NULL); + L3N = strtod(i_L3N, NULL); + + if (fabs(L1N) >= thr) + inphases++; + if (fabs(L2N) >= thr) + inphases++; + if (fabs(L2N) >= thr) + inphases++; + + dstate_setinfo("input.phases", "%u", inphases); + } + + if (o_L1N && o_L2N && o_L3N) { + L1N = strtod(o_L1N, NULL); + L2N = strtod(o_L2N, NULL); + L3N = strtod(o_L3N, NULL); + + if (fabs(L1N) >= thr) + outphases++; + if (fabs(L2N) >= thr) + outphases++; + if (fabs(L2N) >= thr) + outphases++; + + dstate_setinfo("output.phases", "%u", outphases); + } +} + +/* Subdriver interface */ +subdriver_t q2_subdriver = { + Q2_VERSION, + q2_claim, + q2_qx2nut, + q2_initups, + q2_initinfo, + q2_makevartable, + "ACK", + NULL, +#ifdef TESTING + q2_testing, +#endif /* TESTING */ +}; diff --git a/drivers/nutdrv_qx_q2.h b/drivers/nutdrv_qx_q2.h new file mode 100644 index 0000000000..6ce138ca34 --- /dev/null +++ b/drivers/nutdrv_qx_q2.h @@ -0,0 +1,29 @@ +/* nutdrv_qx_q2.h - Subdriver for Q2 Megatec protocol variant + * + * Copyright (C) + * 2024 Viktor Drobot + * + * 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_Q2_H +#define NUTDRV_QX_Q2_H + +#include "nutdrv_qx.h" + +extern subdriver_t q2_subdriver; + +#endif /* NUTDRV_QX_Q2_H */ diff --git a/drivers/nutdrv_qx_q6.c b/drivers/nutdrv_qx_q6.c new file mode 100644 index 0000000000..98b979b29a --- /dev/null +++ b/drivers/nutdrv_qx_q6.c @@ -0,0 +1,342 @@ +/* nutdrv_qx_q6.c - Subdriver for Q6 Megatec protocol variant + * + * Copyright (C) + * 2024 Viktor Drobot + * + * 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 "nut_float.h" /* for fabs() */ +#include "nutdrv_qx.h" +#include "nutdrv_qx_blazer-common.h" + +#include "nutdrv_qx_q6.h" + +#define Q6_VERSION "Q6 0.01" + +/* Support functions */ +static int q6_process_topology_bits(item_t *item, char *value, const size_t valuelen); +static int q6_claim(void); +static void q6_initups(void); +static void q6_initinfo(void); +static void q6_makevartable(void); + +/* qx2nut lookup table */ +static item_t q6_qx2nut[] = { + + /* + * > [Q6\r] + * < [(MMM.M MMM.M MMM.M NN.N PPP.P PPP.P PPP.P RR.R QQQ QQQ QQQ SSS.S VVV.V TT.T ttttt CCC KB ffffffff wwwwwwww YO\r] + * 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 + * 0 1 2 3 4 5 6 7 8 9 10 + */ + + /* Common parameters */ + { "input.L1-N.voltage", 0, NULL, "Q6\r", "", 110, '(', "", 1, 5, "%.1f", 0, NULL, NULL, NULL }, + { "input.L2-N.voltage", 0, NULL, "Q6\r", "", 110, '(', "", 7, 11, "%.1f", 0, NULL, NULL, NULL }, + { "input.L3-N.voltage", 0, NULL, "Q6\r", "", 110, '(', "", 13, 17, "%.1f", 0, NULL, NULL, NULL }, + { "input.frequency", 0, NULL, "Q6\r", "", 110, '(', "", 19, 22, "%.1f", 0, NULL, NULL, NULL }, + { "output.L1-N.voltage", 0, NULL, "Q6\r", "", 110, '(', "", 24, 28, "%.1f", 0, NULL, NULL, NULL }, + { "output.L2-N.voltage", 0, NULL, "Q6\r", "", 110, '(', "", 30, 34, "%.1f", 0, NULL, NULL, NULL }, + { "output.L3-N.voltage", 0, NULL, "Q6\r", "", 110, '(', "", 36, 40, "%.1f", 0, NULL, NULL, NULL }, + { "output.frequency", 0, NULL, "Q6\r", "", 110, '(', "", 42, 45, "%.1f", 0, NULL, NULL, NULL }, + { "output.L1.load", 0, NULL, "Q6\r", "", 110, '(', "", 47, 49, "%.0f", 0, NULL, NULL, NULL }, + { "output.L2.load", 0, NULL, "Q6\r", "", 110, '(', "", 51, 53, "%.0f", 0, NULL, NULL, NULL }, + { "output.L3.load", 0, NULL, "Q6\r", "", 110, '(', "", 55, 57, "%.0f", 0, NULL, NULL, NULL }, + { "ups.temperature", 0, NULL, "Q6\r", "", 110, '(', "", 71, 74, "%.1f", 0, NULL, NULL, NULL }, + { "battery.voltage", 0, NULL, "Q6\r", "", 110, '(', "", 59, 63, "%.1f", 0, NULL, NULL, qx_multiply_battvolt }, + { "battery.runtime", 0, NULL, "Q6\r", "", 110, '(', "", 76, 80, "%.0f", 0, NULL, NULL, NULL }, + { "battery.charge", 0, NULL, "Q6\r", "", 110, '(', "", 82, 84, "%.0f", 0, NULL, NULL, NULL }, + { "experimental.battery.voltage.negative", 0, NULL, "Q6\r", "", 110, '(', "", 65, 69, "%.1f", 0, NULL, NULL, qx_multiply_battvolt }, /* Negative battery voltage */ + { "experimental.input.topology", 0, NULL, "Q6\r", "", 110, '(', "", 107, 107, "%s", QX_FLAG_STATIC, NULL, NULL, q6_process_topology_bits }, /* Input transformer topology */ + { "experimental.output.topology", 0, NULL, "Q6\r", "", 110, '(', "", 108, 108, "%s", QX_FLAG_STATIC, NULL, NULL, q6_process_topology_bits }, /* Output transformer topology */ + /* TODO handle KB (86-87) properly: mb use "experimental." vars for that */ + + /* + * > [WA\r] + * < [(WWW.W WWW.W WWW.W VVV.V VVV.V VVV.V TTT.T SSS.S AAA.A AAA.A AAA.A QQQ b7b6b5b4b3b2b1b0\r] + * 012345678901234567890123456789012345678901234567890123456789012345678901 2 3 4 5 6 7 8 9 + * 0 1 2 3 4 5 6 7 + */ + + /* Output consumption parameters */ + { "output.L1.realpower", 0, NULL, "WA\r", "", 80, '(', "", 1, 5, "%.1f", 0, NULL, NULL, qx_multiply_x1000 }, + { "output.L2.realpower", 0, NULL, "WA\r", "", 80, '(', "", 7, 11, "%.1f", 0, NULL, NULL, qx_multiply_x1000 }, + { "output.L3.realpower", 0, NULL, "WA\r", "", 80, '(', "", 13, 17, "%.1f", 0, NULL, NULL, qx_multiply_x1000 }, + { "output.L1.power", 0, NULL, "WA\r", "", 80, '(', "", 19, 23, "%.1f", 0, NULL, NULL, qx_multiply_x1000 }, + { "output.L2.power", 0, NULL, "WA\r", "", 80, '(', "", 25, 29, "%.1f", 0, NULL, NULL, qx_multiply_x1000 }, + { "output.L3.power", 0, NULL, "WA\r", "", 80, '(', "", 31, 35, "%.1f", 0, NULL, NULL, qx_multiply_x1000 }, + { "output.L1.current", 0, NULL, "WA\r", "", 80, '(', "", 49, 53, "%.1f", 0, NULL, NULL, NULL }, + { "output.L2.current", 0, NULL, "WA\r", "", 80, '(', "", 55, 59, "%.1f", 0, NULL, NULL, NULL }, + { "output.L3.current", 0, NULL, "WA\r", "", 80, '(', "", 61, 65, "%.1f", 0, NULL, NULL, NULL }, + { "ups.realpower", 0, NULL, "WA\r", "", 80, '(', "", 37, 41, "%.1f", 0, NULL, NULL, qx_multiply_x1000 }, + { "ups.power", 0, NULL, "WA\r", "", 80, '(', "", 43, 47, "%.1f", 0, NULL, NULL, qx_multiply_x1000 }, + { "ups.load", 0, NULL, "WA\r", "", 80, '(', "", 67, 69, "%.0f", 0, NULL, NULL, NULL }, + + /* + * > [Q1\r] + * < [(MMM.M NNN.N PPP.P QQQ RR.R S.SS TT.T b7b6b5b4b3b2b1b0\r] + * 012345678901234567890123456789012345678 9 0 1 2 3 4 5 6 + * 0 1 2 3 4 + */ + + /* 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 */ + + /* 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, DEFAULT_ONDELAY, 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, DEFAULT_OFFDELAY, 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 q6_testing[] = { + { "Q6\r", "(227.0 225.6 230.0 50.0 229.9 000.0 000.0 49.9 007 000 000 327.8 000.0 23.0 06932 100 32 00000000 00000000 11\r", -1 }, + { "WA\r", "(001.4 000.0 000.0 001.4 000.0 000.0 001.4 001.4 006.5 000.0 000.0 007 00000000\r", -1 }, + { "Q1\r", "(215.0 195.0 230.0 014 49.0 22.7 30.0 00000000\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 */ + +/* == Support functions == */ + +/* This function allows to process input/output transformers topology bits */ +static int q6_process_topology_bits(item_t *item, char *value, const size_t valuelen) +{ + char *val = ""; + + if (strspn(item->value, "01") != 1) { + upsdebugx(3, "%s: unexpected value %s@%d->%s", __func__, item->value, item->from, item->value); + return -1; + } + + switch (item->from) + { + case 107: /* Input topology - experimental.input.topology */ + + if (item->value[0] == '1') + val = "star"; + else if (item->value[0] == '0') + val = "delta"; + else + val = "unknown"; + break; + + case 108: /* Output topology - experimental.output.topology */ + + if (item->value[0] == '1') + val = "star"; + else if (item->value[0] == '0') + val = "delta"; + else + val = "unknown"; + break; + + default: + /* Don't know what happened */ + return -1; + } + + snprintf(value, valuelen, "%s", val); + + return 0; +} + +/* This function allows the subdriver to "claim" a device: return 1 if the device is supported by this subdriver, else 0 */ +static int q6_claim(void) +{ + /* We need Q6, Q1, and WA to use this subdriver (only if user hasn't requested 'nooutstats' flag */ + struct { + char *var; + char *cmd; + } mandatory[] = { + { "input.L1-N.voltage", "Q6" }, + { "ups.type", "Q1" }, + { "output.L1.current", "WA"}, + { NULL, NULL } + }; + int vari; + char *sp, *cmd; + + for (vari = 0; mandatory[vari].var; vari++) { + sp = mandatory[vari].var; + cmd = mandatory[vari].cmd; + + /* Ignore checks for 'WA' command support if explicitly asked */ + if (testvar("nooutstats") && (strcasecmp(cmd, "WA") == 0)) + continue; + + item_t *item = find_nut_info(sp, 0, 0); + + /* Don't know what happened */ + if (!item) + return 0; + + /* No reply/Unable to get value */ + if (qx_process(item, NULL)) + return 0; + + /* Unable to process value */ + if (ups_infoval_set(item) != 1) + return 0; + } + + return 1; +} + +/* Subdriver-specific initups */ +static void q6_initups(void) +{ + int nr, nos, isb; + item_t *item; + + nr = testvar("norating"); + nos = testvar("nooutstats"); + isb = testvar("ignoresab"); + + if (!nr && !nos && !isb) + return; + + for (item = q6_qx2nut; item->info_type != NULL; item++) { + + if (!item->command) + continue; + + /* norating */ + if (nr && (strcasecmp(item->command, "F\r") == 0)) { + upsdebugx(2, "%s: skipping %s", __func__, item->info_type); + item->qxflags |= QX_FLAG_SKIP; + } + + /* nooutstats */ + if (nos && (strcasecmp(item->command, "WA\r") == 0)) { + upsdebugx(2, "%s: skipping %s", __func__, item->info_type); + item->qxflags |= QX_FLAG_SKIP; + } + + /* ignoresab */ + if (isb && (strcasecmp(item->info_type, "ups.status") == 0) && item->from == 44 && item->to == 44) { + upsdebugx(2, "%s: skipping %s ('Shutdown Active' bit)", __func__, item->info_type); + item->qxflags |= QX_FLAG_SKIP; + } + } +} + +/* Subdriver-specific initinfo */ +static void q6_initinfo(void) +{ + dstate_setinfo("input.phases", "%u", 3); + dstate_setinfo("output.phases", "%u", 3); +} + +/* Subdriver-specific flags/vars */ +static void q6_makevartable(void) +{ + addvar(VAR_FLAG, "norating", "Skip reading rating information from UPS"); + addvar(VAR_FLAG, "nooutstats", "Skip reading output load stats information from UPS"); + + blazer_makevartable_light(); +} + +void upsdrv_updateinfo(void) { + const char *i_L1N = dstate_getinfo("input.L1-N.voltage"); + const char *i_L2N = dstate_getinfo("input.L2-N.voltage"); + const char *i_L3N = dstate_getinfo("input.L3-N.voltage"); + const char *o_L1N = dstate_getinfo("output.L1-N.voltage"); + const char *o_L2N = dstate_getinfo("output.L2-N.voltage"); + const char *o_L3N = dstate_getinfo("output.L3-N.voltage"); + const double thr = 10.0; /* threshold for comparison; mb better to use user-defined variable */ + + unsigned int inphases = 0, outphases = 0; + double L1N, L2N, L3N; + + if (i_L1N && i_L2N && i_L3N) { + L1N = strtod(i_L1N, NULL); + L2N = strtod(i_L2N, NULL); + L3N = strtod(i_L3N, NULL); + + if (fabs(L1N) >= thr) + inphases++; + if (fabs(L2N) >= thr) + inphases++; + if (fabs(L2N) >= thr) + inphases++; + + dstate_setinfo("input.phases", "%u", inphases); + } + + if (o_L1N && o_L2N && o_L3N) { + L1N = strtod(o_L1N, NULL); + L2N = strtod(o_L2N, NULL); + L3N = strtod(o_L3N, NULL); + + if (fabs(L1N) >= thr) + outphases++; + if (fabs(L2N) >= thr) + outphases++; + if (fabs(L2N) >= thr) + outphases++; + + dstate_setinfo("output.phases", "%u", outphases); + } +} + +/* Subdriver interface */ +subdriver_t q6_subdriver = { + Q6_VERSION, + q6_claim, + q6_qx2nut, + q6_initups, + q6_initinfo, + q6_makevartable, + "ACK", + NULL, +#ifdef TESTING + q6_testing, +#endif /* TESTING */ +}; diff --git a/drivers/nutdrv_qx_q6.h b/drivers/nutdrv_qx_q6.h new file mode 100644 index 0000000000..c9ede053da --- /dev/null +++ b/drivers/nutdrv_qx_q6.h @@ -0,0 +1,29 @@ +/* nutdrv_qx_q6.h - Subdriver for Q6 Megatec protocol variant + * + * Copyright (C) + * 2024 Viktor Drobot + * + * 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_Q6_H +#define NUTDRV_QX_Q6_H + +#include "nutdrv_qx.h" + +extern subdriver_t q6_subdriver; + +#endif /* NUTDRV_QX_Q6_H */ From 3a3d70d83f2e667fcf342b3433b4eb1b53ca3260 Mon Sep 17 00:00:00 2001 From: Viktor Drobot Date: Sun, 9 Feb 2025 14:01:48 +0300 Subject: [PATCH 02/10] Remove phase detection in updateinfo. Calm down spellchecker Signed-off-by: Viktor Drobot --- docs/nut.dict | 1 + drivers/nutdrv_qx_innovart31.c | 3 +-- drivers/nutdrv_qx_q2.c | 45 +--------------------------------- drivers/nutdrv_qx_q6.c | 45 +--------------------------------- 4 files changed, 4 insertions(+), 90 deletions(-) diff --git a/docs/nut.dict b/docs/nut.dict index ab282cb7cd..dcfae08263 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -2481,6 +2481,7 @@ nolock nombattvolt noncommercially noout +nooutstats norating noro noscanlangid diff --git a/drivers/nutdrv_qx_innovart31.c b/drivers/nutdrv_qx_innovart31.c index ceffde8abf..656efb3143 100644 --- a/drivers/nutdrv_qx_innovart31.c +++ b/drivers/nutdrv_qx_innovart31.c @@ -190,11 +190,10 @@ static int innovart31_claim(void) { NULL, NULL} }; int vari; - char *sp, *cmd; + char *sp; for (vari = 0; mandatory[vari].var; vari++) { sp = mandatory[vari].var; - cmd = mandatory[vari].cmd; item_t *item = find_nut_info(sp, 0, 0); diff --git a/drivers/nutdrv_qx_q2.c b/drivers/nutdrv_qx_q2.c index 4549491a34..ebd75e58c3 100644 --- a/drivers/nutdrv_qx_q2.c +++ b/drivers/nutdrv_qx_q2.c @@ -20,7 +20,6 @@ */ #include "main.h" -#include "nut_float.h" /* for fabs() */ #include "nutdrv_qx.h" #include "nutdrv_qx_blazer-common.h" @@ -190,6 +189,7 @@ static int q2_process_topology_bits(item_t *item, char *value, const size_t valu static int q2_claim(void) { /* We need Q2, Q1, and WA to use this subdriver (only if user hasn't requested 'nooutstats' flag */ + /* TODO think whether we should check for BPS (and bypass-related variables) too */ struct { char *var; char *cmd; @@ -282,49 +282,6 @@ static void q2_makevartable(void) blazer_makevartable_light(); } -void upsdrv_updateinfo(void) { - const char *i_L1N = dstate_getinfo("input.L1-N.voltage"); - const char *i_L2N = dstate_getinfo("input.L2-N.voltage"); - const char *i_L3N = dstate_getinfo("input.L3-N.voltage"); - const char *o_L1N = dstate_getinfo("output.L1-N.voltage"); - const char *o_L2N = dstate_getinfo("output.L2-N.voltage"); - const char *o_L3N = dstate_getinfo("output.L3-N.voltage"); - const double thr = 10.0; /* threshold for comparison; mb better to use user-defined variable */ - - unsigned int inphases = 0, outphases = 0; - double L1N, L2N, L3N; - - if (i_L1N && i_L2N && i_L3N) { - L1N = strtod(i_L1N, NULL); - L2N = strtod(i_L2N, NULL); - L3N = strtod(i_L3N, NULL); - - if (fabs(L1N) >= thr) - inphases++; - if (fabs(L2N) >= thr) - inphases++; - if (fabs(L2N) >= thr) - inphases++; - - dstate_setinfo("input.phases", "%u", inphases); - } - - if (o_L1N && o_L2N && o_L3N) { - L1N = strtod(o_L1N, NULL); - L2N = strtod(o_L2N, NULL); - L3N = strtod(o_L3N, NULL); - - if (fabs(L1N) >= thr) - outphases++; - if (fabs(L2N) >= thr) - outphases++; - if (fabs(L2N) >= thr) - outphases++; - - dstate_setinfo("output.phases", "%u", outphases); - } -} - /* Subdriver interface */ subdriver_t q2_subdriver = { Q2_VERSION, diff --git a/drivers/nutdrv_qx_q6.c b/drivers/nutdrv_qx_q6.c index 98b979b29a..24c61ceb25 100644 --- a/drivers/nutdrv_qx_q6.c +++ b/drivers/nutdrv_qx_q6.c @@ -20,7 +20,6 @@ */ #include "main.h" -#include "nut_float.h" /* for fabs() */ #include "nutdrv_qx.h" #include "nutdrv_qx_blazer-common.h" @@ -191,6 +190,7 @@ static int q6_process_topology_bits(item_t *item, char *value, const size_t valu static int q6_claim(void) { /* We need Q6, Q1, and WA to use this subdriver (only if user hasn't requested 'nooutstats' flag */ + /* TODO think whether we should check for BPS (and bypass-related variables) too */ struct { char *var; char *cmd; @@ -283,49 +283,6 @@ static void q6_makevartable(void) blazer_makevartable_light(); } -void upsdrv_updateinfo(void) { - const char *i_L1N = dstate_getinfo("input.L1-N.voltage"); - const char *i_L2N = dstate_getinfo("input.L2-N.voltage"); - const char *i_L3N = dstate_getinfo("input.L3-N.voltage"); - const char *o_L1N = dstate_getinfo("output.L1-N.voltage"); - const char *o_L2N = dstate_getinfo("output.L2-N.voltage"); - const char *o_L3N = dstate_getinfo("output.L3-N.voltage"); - const double thr = 10.0; /* threshold for comparison; mb better to use user-defined variable */ - - unsigned int inphases = 0, outphases = 0; - double L1N, L2N, L3N; - - if (i_L1N && i_L2N && i_L3N) { - L1N = strtod(i_L1N, NULL); - L2N = strtod(i_L2N, NULL); - L3N = strtod(i_L3N, NULL); - - if (fabs(L1N) >= thr) - inphases++; - if (fabs(L2N) >= thr) - inphases++; - if (fabs(L2N) >= thr) - inphases++; - - dstate_setinfo("input.phases", "%u", inphases); - } - - if (o_L1N && o_L2N && o_L3N) { - L1N = strtod(o_L1N, NULL); - L2N = strtod(o_L2N, NULL); - L3N = strtod(o_L3N, NULL); - - if (fabs(L1N) >= thr) - outphases++; - if (fabs(L2N) >= thr) - outphases++; - if (fabs(L2N) >= thr) - outphases++; - - dstate_setinfo("output.phases", "%u", outphases); - } -} - /* Subdriver interface */ subdriver_t q6_subdriver = { Q6_VERSION, From d638ea360aa67e98b255c300ba2a432bfe30f229 Mon Sep 17 00:00:00 2001 From: Viktor Drobot Date: Sun, 9 Feb 2025 14:15:34 +0300 Subject: [PATCH 03/10] Separate code and variable declarations Signed-off-by: Viktor Drobot --- drivers/nutdrv_qx_innovart31.c | 4 ++-- drivers/nutdrv_qx_q2.c | 3 ++- drivers/nutdrv_qx_q6.c | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/nutdrv_qx_innovart31.c b/drivers/nutdrv_qx_innovart31.c index 656efb3143..3f75340587 100644 --- a/drivers/nutdrv_qx_innovart31.c +++ b/drivers/nutdrv_qx_innovart31.c @@ -191,11 +191,11 @@ static int innovart31_claim(void) }; int vari; char *sp; + item_t *item; for (vari = 0; mandatory[vari].var; vari++) { sp = mandatory[vari].var; - - item_t *item = find_nut_info(sp, 0, 0); + item = find_nut_info(sp, 0, 0); /* Don't know what happened */ if (!item) diff --git a/drivers/nutdrv_qx_q2.c b/drivers/nutdrv_qx_q2.c index ebd75e58c3..b724060a78 100644 --- a/drivers/nutdrv_qx_q2.c +++ b/drivers/nutdrv_qx_q2.c @@ -201,6 +201,7 @@ static int q2_claim(void) }; int vari; char *sp, *cmd; + item_t *item; for (vari = 0; mandatory[vari].var; vari++) { sp = mandatory[vari].var; @@ -210,7 +211,7 @@ static int q2_claim(void) if (testvar("nooutstats") && (strcasecmp(cmd, "WA") == 0)) continue; - item_t *item = find_nut_info(sp, 0, 0); + item = find_nut_info(sp, 0, 0); /* Don't know what happened */ if (!item) diff --git a/drivers/nutdrv_qx_q6.c b/drivers/nutdrv_qx_q6.c index 24c61ceb25..2090a81eb0 100644 --- a/drivers/nutdrv_qx_q6.c +++ b/drivers/nutdrv_qx_q6.c @@ -202,6 +202,7 @@ static int q6_claim(void) }; int vari; char *sp, *cmd; + item_t *item; for (vari = 0; mandatory[vari].var; vari++) { sp = mandatory[vari].var; @@ -211,7 +212,7 @@ static int q6_claim(void) if (testvar("nooutstats") && (strcasecmp(cmd, "WA") == 0)) continue; - item_t *item = find_nut_info(sp, 0, 0); + item = find_nut_info(sp, 0, 0); /* Don't know what happened */ if (!item) From 3630e287d9f905918151fd2069b968cad9e9e408 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 9 Feb 2025 13:50:23 +0100 Subject: [PATCH 04/10] docs/man/nutdrv_qx.txt: typo fixes for `nooutstats` [#2798] Signed-off-by: Jim Klimov --- docs/man/nutdrv_qx.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/man/nutdrv_qx.txt b/docs/man/nutdrv_qx.txt index 8cc344d88e..810f6a0f45 100644 --- a/docs/man/nutdrv_qx.txt +++ b/docs/man/nutdrv_qx.txt @@ -222,7 +222,7 @@ Q2, Q6 PROTOCOLS ~~~~~~~~~~~~~~~~ *nooutstats*:: -Some UPSes doesn't support `WA' command which returns output load stats. Using this flag will make the driver ignore these requests. +Some UPSes don't support `WA` command which returns output load stats. Using this flag will make the driver ignore these requests. VOLTRONIC-QS, VOLTRONIC-QS-HEX PROTOCOLS From dfe2d34efbc3b447d7dcbb5a18c8f8806619e047 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 9 Feb 2025 13:53:04 +0100 Subject: [PATCH 05/10] drivers/nutdrv_qx.c: bump DRIVER_VERSION for Q2/Q6 addition [#2798] Signed-off-by: Jim Klimov --- drivers/nutdrv_qx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nutdrv_qx.c b/drivers/nutdrv_qx.c index 8b53e3b445..c16171454e 100644 --- a/drivers/nutdrv_qx.c +++ b/drivers/nutdrv_qx.c @@ -58,7 +58,7 @@ # define DRIVER_NAME "Generic Q* Serial driver" #endif /* QX_USB */ -#define DRIVER_VERSION "0.39" +#define DRIVER_VERSION "0.40" #ifdef QX_SERIAL # include "serial.h" From e2621ebc3a896d678267453c3605841e09cc1c78 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 9 Feb 2025 14:11:03 +0100 Subject: [PATCH 06/10] drivers/nutdrv_qx_q2.c: q2_claim(): revise comment Signed-off-by: Jim Klimov --- drivers/nutdrv_qx_q2.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/nutdrv_qx_q2.c b/drivers/nutdrv_qx_q2.c index b724060a78..003cc23199 100644 --- a/drivers/nutdrv_qx_q2.c +++ b/drivers/nutdrv_qx_q2.c @@ -185,10 +185,12 @@ static int q2_process_topology_bits(item_t *item, char *value, const size_t valu return 0; } -/* This function allows the subdriver to "claim" a device: return 1 if the device is supported by this subdriver, else 0 */ +/* This function allows the subdriver to "claim" a device: + * return 1 if the device is supported by this subdriver, else 0 + */ static int q2_claim(void) { - /* We need Q2, Q1, and WA to use this subdriver (only if user hasn't requested 'nooutstats' flag */ + /* We need Q2, Q1, and WA to use this subdriver (WA only if user hasn't requested 'nooutstats' flag) */ /* TODO think whether we should check for BPS (and bypass-related variables) too */ struct { char *var; From 1856ec86402da439038453a0c0906f1643728f49 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 9 Feb 2025 14:11:46 +0100 Subject: [PATCH 07/10] drivers/nutdrv_qx_q6.c: q6_claim(): revise comment Signed-off-by: Jim Klimov --- drivers/nutdrv_qx_q6.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/nutdrv_qx_q6.c b/drivers/nutdrv_qx_q6.c index 2090a81eb0..a135bd5995 100644 --- a/drivers/nutdrv_qx_q6.c +++ b/drivers/nutdrv_qx_q6.c @@ -186,10 +186,12 @@ static int q6_process_topology_bits(item_t *item, char *value, const size_t valu return 0; } -/* This function allows the subdriver to "claim" a device: return 1 if the device is supported by this subdriver, else 0 */ +/* This function allows the subdriver to "claim" a device: + * return 1 if the device is supported by this subdriver, else 0 + */ static int q6_claim(void) { - /* We need Q6, Q1, and WA to use this subdriver (only if user hasn't requested 'nooutstats' flag */ + /* We need Q6, Q1, and WA to use this subdriver (WA only if user hasn't requested 'nooutstats' flag) */ /* TODO think whether we should check for BPS (and bypass-related variables) too */ struct { char *var; From d0ddc32950e87fdbbb217df9755b16972d40ad98 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 9 Feb 2025 14:16:23 +0100 Subject: [PATCH 08/10] NEWS.adoc: introduction of Q2 and Q6 protocol support in nutdrv_qx Signed-off-by: Jim Klimov --- NEWS.adoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/NEWS.adoc b/NEWS.adoc index f5b44de78e..bad715de33 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -178,7 +178,9 @@ https://github.com/networkupstools/nut/milestone/11 * added Visench C1K (using serial port converter with USB ID `1a86:7523`) as known supported by `nutdrv_qx` (Megatec protocol) since at least NUT v2.7.4 release. [#2395] - * introduced `innovart31` protocol support for Innova RT 3/1 UPSes. [#2712] + * 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] * extended Voltronic protocol to support longer numbers as remaining `battery.runtime` value. [#2765] From ae3b8712a5ef3203a51ae275dff78e9dd716093d Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 9 Feb 2025 15:08:21 +0100 Subject: [PATCH 09/10] drivers/nutdrv_qx_q6.c: q6_initinfo(): fix formatting string Signed-off-by: Jim Klimov --- drivers/nutdrv_qx_q6.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/nutdrv_qx_q6.c b/drivers/nutdrv_qx_q6.c index a135bd5995..243d44d7b5 100644 --- a/drivers/nutdrv_qx_q6.c +++ b/drivers/nutdrv_qx_q6.c @@ -273,8 +273,8 @@ static void q6_initups(void) /* Subdriver-specific initinfo */ static void q6_initinfo(void) { - dstate_setinfo("input.phases", "%u", 3); - dstate_setinfo("output.phases", "%u", 3); + dstate_setinfo("input.phases", "%d", 3); + dstate_setinfo("output.phases", "%d", 3); } /* Subdriver-specific flags/vars */ From c4dac8414ba8bebad920f56755c4ba722481c0fb Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 9 Feb 2025 15:08:24 +0100 Subject: [PATCH 10/10] drivers/nutdrv_qx_q2.c: q2_initinfo(): fix formatting string Signed-off-by: Jim Klimov --- drivers/nutdrv_qx_q2.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/nutdrv_qx_q2.c b/drivers/nutdrv_qx_q2.c index 003cc23199..4f2cd8abd3 100644 --- a/drivers/nutdrv_qx_q2.c +++ b/drivers/nutdrv_qx_q2.c @@ -272,8 +272,8 @@ static void q2_initups(void) /* Subdriver-specific initinfo */ static void q2_initinfo(void) { - dstate_setinfo("input.phases", "%u", 3); - dstate_setinfo("output.phases", "%u", 3); + dstate_setinfo("input.phases", "%d", 3); + dstate_setinfo("output.phases", "%d", 3); } /* Subdriver-specific flags/vars */