From 1595a06501daa93e06035a861e3db7ccab2871dd Mon Sep 17 00:00:00 2001 From: Daniele Pezzini Date: Sat, 28 Jul 2018 22:13:24 +0200 Subject: [PATCH 01/16] nutdrv_qx: support USB devices that don't close their replies with a CR Since some devices, when communicating via USB, don't close their replies to our commands/queries with the expected (and mandated by the specs) CR, rendering the driver almost useless as protocols get one less character than they expect, update the various USB subdrivers (leaving out the ones that rely on a CR to stop reading from the device) to add the missing terminating CR in such cases (as long as we get anything usable). This is a bit of a cheat, but, at least for now, it will do -- not to mention the fact that it is way less invasive than touching all the places of the driver that expect a closing CR and all the qx2nut tables of the various protocols. Close https://github.com/networkupstools/nut/issues/441 --- drivers/nutdrv_qx.c | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/drivers/nutdrv_qx.c b/drivers/nutdrv_qx.c index bb526608c7..d8345fb9ae 100644 --- a/drivers/nutdrv_qx.c +++ b/drivers/nutdrv_qx.c @@ -33,7 +33,7 @@ * */ -#define DRIVER_VERSION "0.28" +#define DRIVER_VERSION "0.29" #include "main.h" @@ -540,6 +540,16 @@ static int sgs_command(const char *cmd, char *buf, size_t buflen) } + /* If the reply lacks the expected terminating CR, add it (if there's enough space) */ + if (i && memchr(buf, '\r', i) == NULL) { + upsdebugx(4, "%s: the reply lacks the expected terminating CR.", __func__); + if (i < buflen - 1) { + upsdebugx(4, "%s: adding missing terminating CR.", __func__); + buf[i++] = '\r'; + buf[i] = 0; + } + } + upsdebugx(3, "read: %.*s", (int)strcspn(buf, "\r"), buf); return i; } @@ -677,6 +687,16 @@ static int ippon_command(const char *cmd, char *buf, size_t buflen) memset(buf, 0, buflen); memcpy(buf, tmp, len); + /* If the reply lacks the expected terminating CR, add it (if there's enough space) */ + if (len && memchr(buf, '\r', len) == NULL) { + upsdebugx(4, "%s: the reply lacks the expected terminating CR.", __func__); + if (len < buflen - 1) { + upsdebugx(4, "%s: adding missing terminating CR.", __func__); + buf[len++] = '\r'; + buf[len] = 0; + } + } + return (int)len; } @@ -761,6 +781,16 @@ static int krauler_command(const char *cmd, char *buf, size_t buflen) ret = di; } + /* If the reply lacks the expected terminating CR, add it (if there's enough space) */ + if (ret && memchr(buf, '\r', ret) == NULL) { + upsdebugx(4, "%s: the reply lacks the expected terminating CR.", __func__); + if ((size_t)ret < buflen - 1) { + upsdebugx(4, "%s: adding missing terminating CR.", __func__); + buf[ret++] = '\r'; + buf[ret] = 0; + } + } + /* "UPS No Ack" has a special meaning */ if ( strcspn(buf, "\r") == 10 && @@ -867,6 +897,16 @@ static int fabula_command(const char *cmd, char *buf, size_t buflen) return ret; } + /* If the reply lacks the expected terminating CR, add it (if there's enough space) */ + if (memchr(buf, '\r', ret) == NULL) { + upsdebugx(4, "%s: the reply lacks the expected terminating CR.", __func__); + if ((size_t)ret < buflen - 1) { + upsdebugx(4, "%s: adding missing terminating CR.", __func__); + buf[ret++] = '\r'; + buf[ret] = 0; + } + } + upsdebug_hex(5, "read", buf, ret); upsdebugx(3, "read: %.*s", (int)strcspn(buf, "\r"), buf); From 258276ac1a1892690fe5e4472309ae5d8804efeb Mon Sep 17 00:00:00 2001 From: Gabriele Taormina Date: Mon, 27 Aug 2018 10:59:52 +0200 Subject: [PATCH 02/16] nutdrv_qx: add support for some Legrand USB devices --- data/driver.list.in | 4 ++++ drivers/nutdrv_qx.c | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/data/driver.list.in b/data/driver.list.in index eb790ac91f..cd18d51f8d 100644 --- a/data/driver.list.in +++ b/data/driver.list.in @@ -513,6 +513,10 @@ "LDLC" "ups" "2" "UPS-1200D" "" "blazer_usb langid_fix=0x4095" +"Legrand" "ups" "2" "Daker DK" "Serial" "nutdrv_qx" +"Legrand" "ups" "2" "Daker DK" "USB" "nutdrv_qx" +"Legrand" "ups" "2" "Daker DK Plus" "Serial" "nutdrv_qx" +"Legrand" "ups" "2" "Daker DK Plus" "USB" "nutdrv_qx" "Legrand" "ups" "2" "Keor Multiplug" "" "nutdrv_qx" "Lestar" "ups" "2" "MD-800E" "" "blazer_ser" diff --git a/drivers/nutdrv_qx.c b/drivers/nutdrv_qx.c index d8345fb9ae..78f6db605e 100644 --- a/drivers/nutdrv_qx.c +++ b/drivers/nutdrv_qx.c @@ -33,7 +33,7 @@ * */ -#define DRIVER_VERSION "0.29" +#define DRIVER_VERSION "0.30" #include "main.h" @@ -1102,6 +1102,7 @@ typedef struct { static qx_usb_device_id_t qx_usb_id[] = { { USB_DEVICE(0x05b8, 0x0000), NULL, NULL, &cypress_subdriver }, /* Agiler UPS */ { USB_DEVICE(0xffff, 0x0000), NULL, NULL, &krauler_subdriver }, /* Ablerex 625L USB */ + { USB_DEVICE(0x1cb0, 0x0035), NULL, NULL, &krauler_subdriver }, /* Legrand Daker DK / DK Plus */ { USB_DEVICE(0x0665, 0x5161), NULL, NULL, &cypress_subdriver }, /* Belkin F6C1200-UNV/Voltronic Power UPSes */ { USB_DEVICE(0x06da, 0x0002), NULL, NULL, &cypress_subdriver }, /* Online Yunto YQ450 */ { USB_DEVICE(0x06da, 0x0003), NULL, NULL, &ippon_subdriver }, /* Mustek Powermust */ From 8a5e87ac871b05339eb5111ba6e9f76c85adcc4d Mon Sep 17 00:00:00 2001 From: Gabriele Taormina Date: Mon, 27 Aug 2018 11:24:37 +0200 Subject: [PATCH 03/16] usbhid-ups: add Legrand subdriver --- data/driver.list.in | 2 + drivers/Makefile.am | 4 +- drivers/legrand-hid.c | 228 ++++++++++++++++++++++++++++++++++++++++++ drivers/legrand-hid.h | 30 ++++++ drivers/usbhid-ups.c | 4 +- 5 files changed, 265 insertions(+), 3 deletions(-) create mode 100644 drivers/legrand-hid.c create mode 100644 drivers/legrand-hid.h diff --git a/data/driver.list.in b/data/driver.list.in index cd18d51f8d..e7e91d1f71 100644 --- a/data/driver.list.in +++ b/data/driver.list.in @@ -518,6 +518,8 @@ "Legrand" "ups" "2" "Daker DK Plus" "Serial" "nutdrv_qx" "Legrand" "ups" "2" "Daker DK Plus" "USB" "nutdrv_qx" "Legrand" "ups" "2" "Keor Multiplug" "" "nutdrv_qx" +"Legrand" "ups" "2" "Keor PDU" "USB" "usbhid-ups" +"Legrand" "ups" "2" "Keor SP" "USB" "usbhid-ups" "Lestar" "ups" "2" "MD-800E" "" "blazer_ser" diff --git a/drivers/Makefile.am b/drivers/Makefile.am index e04989cf83..cf3f02d1a6 100644 --- a/drivers/Makefile.am +++ b/drivers/Makefile.am @@ -170,7 +170,7 @@ skel_LDADD = $(LDADD_DRIVERS) # USB USBHID_UPS_SUBDRIVERS = apc-hid.c belkin-hid.c cps-hid.c explore-hid.c \ liebert-hid.c mge-hid.c powercom-hid.c tripplite-hid.c idowell-hid.c \ - openups-hid.c + openups-hid.c legrand-hid.c usbhid_ups_SOURCES = usbhid-ups.c libhid.c libusb.c hidparser.c \ usb-common.c $(USBHID_UPS_SUBDRIVERS) usbhid_ups_LDADD = $(LDADD_DRIVERS) $(LIBUSB_LIBS) -lm @@ -279,7 +279,7 @@ dist_noinst_HEADERS = apc-mib.h apc-hid.h baytech-mib.h bcmxcp.h \ nutdrv_qx_voltronic.h nutdrv_qx_voltronic-qs.h nutdrv_qx_voltronic-qs-hex.h nutdrv_qx_zinto.h \ xppc-mib.h huawei-mib.h eaton-ats16-mib.h apc-ats-mib.h raritan-px2-mib.h eaton-ats30-mib.h \ apc-pdu-mib.h eaton-pdu-genesis2-mib.h eaton-pdu-marlin-mib.h \ - eaton-pdu-pulizzi-mib.h eaton-pdu-revelation-mib.h emerson-avocent-pdu-mib.h + eaton-pdu-pulizzi-mib.h eaton-pdu-revelation-mib.h emerson-avocent-pdu-mib.h legrand-hid.h # Define a dummy library so that Automake builds rules for the # corresponding object files. This library is not actually built, diff --git a/drivers/legrand-hid.c b/drivers/legrand-hid.c new file mode 100644 index 0000000000..67ad91521e --- /dev/null +++ b/drivers/legrand-hid.c @@ -0,0 +1,228 @@ +/* legrand-hid.c - subdriver to monitor Legrand USB/HID devices with NUT + * + * Copyright (C) + * 2003 - 2012 Arnaud Quette + * 2005 - 2006 Peter Selinger + * 2008 - 2009 Arjen de Korte + * 2013 Charles Lepple + * 2018 Gabriele Taormina , for Legrand + * + * 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 "usbhid-ups.h" +#include "legrand-hid.h" +#include "main.h" +#include "usb-common.h" + +#define LEGRAND_HID_VERSION "Legrand HID 0.1" + +/* Legrand VendorID and ProductID */ +#define LEGRAND_VID 0x1cb0 /* Legrand */ +#define LEGRAND_PID_PDU 0x0038 /* Keor PDU model (800VA) */ +#define LEGRAND_PID_SP 0x0032 /* Keor SP model (600, 800, 1000, 1500, 2000VA version) */ + +static void *disable_interrupt_pipe(USBDevice_t *device) +{ + if (!use_interrupt_pipe) + return NULL; + use_interrupt_pipe = FALSE; + upslogx(LOG_INFO, "interrupt pipe disabled (add 'pollonly' flag to 'ups.conf' to get rid of this message)"); + return NULL; +} + +/* USB IDs device table */ +static usb_device_id_t legrand_usb_device_table[] = { + { USB_DEVICE(LEGRAND_VID, LEGRAND_PID_PDU), disable_interrupt_pipe }, /* Legrand Keor PDU */ + { USB_DEVICE(LEGRAND_VID, LEGRAND_PID_SP), disable_interrupt_pipe }, /* Legrand Keor SP */ + + /* Terminating entry */ + { -1, -1, NULL } +}; + +/* --------------------------------------------------------------- */ +/* Vendor-specific usage table */ +/* --------------------------------------------------------------- */ + +/* LEGRAND usage table */ +static usage_lkp_t legrand_usage_lkp[] = { + { NULL, 0 } +}; + +static usage_tables_t legrand_utab[] = { + legrand_usage_lkp, + hid_usage_lkp, + NULL, +}; + +static const char *legrand_times10(double value) +{ + static char buf[20]; + snprintf(buf, sizeof(buf), "%0.1f", value * 10); + return buf; +} + +static info_lkp_t legrand_times10_info[] = { + { 0, NULL, legrand_times10 } +}; + +static const char *legrand_times100k(double value) +{ + static char buf[20]; + snprintf(buf, sizeof(buf), "%0.1f", value * 100000); + return buf; +} + +static info_lkp_t legrand_times100k_info[] = { + { 0, NULL, legrand_times100k } +}; + +static const char *legrand_times1M(double value) +{ + static char buf[20]; + snprintf(buf, sizeof(buf), "%0.1f", value * 1000000); + return buf; +} + +static info_lkp_t legrand_times1M_info[] = { + { 0, NULL, legrand_times1M } +}; + +static const char *legrand_times10M(double value) +{ + static char buf[20]; + snprintf(buf, sizeof(buf), "%0.1f", value * 10000000); + return buf; +} + +static info_lkp_t legrand_times10M_info[] = { + { 0, NULL, legrand_times10M } +}; + +/* --------------------------------------------------------------- */ +/* HID2NUT lookup table */ +/* --------------------------------------------------------------- */ + +static hid_info_t legrand_hid2nut[] = { + /* Input Data */ + { "input.voltage", 0, 0, "UPS.Input.Voltage", NULL, "%.0f", 0, legrand_times10_info }, + { "input.voltage", 0, 0, "UPS.PowerConverter.Input.Voltage", NULL, "%.0f", 0, legrand_times1M_info }, + { "input.transfer.high", 0, 0, "UPS.Input.HighVoltageTransfer", NULL, "%.0f", HU_FLAG_STATIC, NULL }, + { "input.transfer.high", 0, 0, "UPS.PowerConverter.Output.HighVoltageTransfer", NULL, "%.0f", HU_FLAG_STATIC, legrand_times10_info }, + { "input.transfer.low", 0, 0, "UPS.Input.LowVoltageTransfer", NULL, "%.0f", HU_FLAG_STATIC, NULL }, + { "input.transfer.low", 0, 0, "UPS.PowerConverter.Output.LowVoltageTransfer", NULL, "%.0f", HU_FLAG_STATIC, NULL }, + { "input.voltage.nominal", 0, 0, "UPS.Input.ConfigVoltage", NULL, "%.0f", HU_FLAG_STATIC, NULL }, + { "input.voltage.nominal", 0, 0, "UPS.Flow.ConfigVoltage", NULL, "%.0f", HU_FLAG_STATIC, NULL }, + + /* Battery Data */ + { "battery.voltage.nominal", 0, 0, "UPS.PowerSummary.ConfigVoltage", NULL, "%.0f", HU_FLAG_STATIC, divide_by_10_conversion }, + { "battery.voltage.nominal", 0, 0, "UPS.BatterySystem.Battery.ConfigVoltage", NULL, "%.0f", HU_FLAG_STATIC, NULL }, + { "battery.voltage", 0, 0, "UPS.PowerSummary.Voltage", NULL, "%.0f", 0, divide_by_10_conversion }, + { "battery.voltage", 0, 0, "UPS.BatterySystem.Battery.Voltage", NULL, "%.0f", 0, legrand_times100k_info }, + { "battery.charge", 0, 0, "UPS.PowerSummary.RemainingCapacity", NULL, "%.0f", 0, NULL }, + { "battery.runtime", 0, 0, "UPS.PowerSummary.RuntimeToEmpty", NULL, "%.0f", 0, NULL }, + { "battery.charge.warning", 0, 0, "UPS.PowerSummary.WarningCapacityLimit", NULL, "%.0f", HU_FLAG_STATIC, NULL }, + { "battery.charge.low", 0, 0, "UPS.PowerSummary.RemainingCapacityLimit", NULL, "%.0f", HU_FLAG_STATIC, NULL }, + + /* Output Data */ + { "output.voltage", 0, 0, "UPS.Output.Voltage", NULL, "%.0f", 0, NULL }, + { "output.voltage", 0, 0, "UPS.PowerConverter.Output.Voltage", NULL, "%.0f", 0, legrand_times10M_info }, + { "output.frequency", 0, 0, "UPS.Output.Frequency", NULL, "%.0f", 0, NULL }, + { "ups.load", 0, 0, "UPS.Output.PercentLoad", NULL, "%.0f", 0, NULL }, + { "ups.load", 0, 0, "UPS.OutletSystem.Outlet.PercentLoad", NULL, "%.0f", 0, NULL }, + { "ups.realpower.nominal", 0, 0, "UPS.Output.ConfigActivePower", NULL, "%.0f", HU_FLAG_STATIC, NULL }, + { "ups.realpower.nominal", 0, 0, "UPS.Flow.ConfigApparentPower", NULL, "%.0f", HU_FLAG_STATIC, NULL }, + + /* UPS Status */ + { "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.ACPresent", NULL, NULL, HU_FLAG_QUICK_POLL, online_info }, + { "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.BelowRemainingCapacityLimit", NULL, NULL, HU_FLAG_QUICK_POLL, lowbatt_info }, + { "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.Charging", NULL, NULL, HU_FLAG_QUICK_POLL, charging_info }, + { "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.Discharging", NULL, NULL, HU_FLAG_QUICK_POLL, discharging_info }, + { "BOOL", 0, 0, "UPS.Output.Overload", NULL, NULL, HU_FLAG_QUICK_POLL, overload_info }, + + /* Delays */ + { "ups.delay.shutdown", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.OutletSystem.Outlet.DelayBeforeShutdown", NULL, DEFAULT_OFFDELAY, HU_FLAG_ABSENT, NULL }, + { "ups.delay.start", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.OutletSystem.Outlet.DelayBeforeStartup", NULL, DEFAULT_ONDELAY, HU_FLAG_ABSENT, NULL }, + { "ups.delay.shutdown", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.Output.DelayBeforeShutdown", NULL, DEFAULT_OFFDELAY, HU_FLAG_ABSENT, NULL }, + { "ups.delay.start", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.Output.DelayBeforeStartup", NULL, DEFAULT_ONDELAY, HU_FLAG_ABSENT, NULL }, + { "load.off.delay", 0, 0, "UPS.OutletSystem.Outlet.DelayBeforeShutdown", NULL, DEFAULT_OFFDELAY, HU_TYPE_CMD, NULL }, + { "load.on.delay", 0, 0, "UPS.OutletSystem.Outlet.DelayBeforeStartup", NULL, DEFAULT_ONDELAY, HU_TYPE_CMD, NULL }, + { "load.off.delay", 0, 0, "UPS.Output.DelayBeforeShutdown", NULL, DEFAULT_OFFDELAY, HU_TYPE_CMD, NULL }, + { "load.on.delay", 0, 0, "UPS.Output.DelayBeforeStartup", NULL, DEFAULT_ONDELAY, HU_TYPE_CMD, NULL }, + + /* Battery Testing */ + { "test.battery.start.quick", 0, 0, "UPS.BatterySystem.Battery.Test", NULL, "1", HU_TYPE_CMD, NULL }, + { "test.battery.start.deep", 0, 0, "UPS.BatterySystem.Battery.Test", NULL, "2", HU_TYPE_CMD, NULL }, + { "test.battery.stop", 0, 0, "UPS.BatterySystem.Battery.Test", NULL, "3", HU_TYPE_CMD, NULL }, + { "test.battery.start.quick", 0, 0, "UPS.Output.Test", NULL, "1", HU_TYPE_CMD, NULL }, + { "test.battery.start.deep", 0, 0, "UPS.Output.Test", NULL, "2", HU_TYPE_CMD, NULL }, + { "test.battery.stop", 0, 0, "UPS.Output.Test", NULL, "3", HU_TYPE_CMD, NULL }, + + /* Buzzer */ + { "beeper.enable", 0, 0, "UPS.PowerSummary.AudibleAlarmControl", NULL, "2", HU_TYPE_CMD, NULL }, + { "beeper.disable", 0, 0, "UPS.PowerSummary.AudibleAlarmControl", NULL, "3", HU_TYPE_CMD, NULL }, + + /* end of structure. */ + { NULL, 0, 0, NULL, NULL, NULL, 0, NULL } +}; + +static const char *legrand_format_model(HIDDevice_t *hd) +{ + return hd->Product; +} + +static const char *legrand_format_mfr(HIDDevice_t *hd) +{ + return hd->Vendor ? hd->Vendor : "Legrand"; +} + +static const char *legrand_format_serial(HIDDevice_t *hd) +{ + return hd->Serial; +} + +/* this function allows the subdriver to "claim" a device: return 1 if + * the device is supported by this subdriver, else 0. */ +static int legrand_claim(HIDDevice_t *hd) +{ + int status = is_usb_device_supported(legrand_usb_device_table, hd); + + switch (status) + { + case POSSIBLY_SUPPORTED: + /* by default, reject, unless the productid option is given */ + if (getval("productid")) + return 1; + possibly_supported("Legrand", hd); + return 0; + + case SUPPORTED: + return 1; + + case NOT_SUPPORTED: + default: + return 0; + } +} + +subdriver_t legrand_subdriver = { + LEGRAND_HID_VERSION, + legrand_claim, + legrand_utab, + legrand_hid2nut, + legrand_format_model, + legrand_format_mfr, + legrand_format_serial, +}; diff --git a/drivers/legrand-hid.h b/drivers/legrand-hid.h new file mode 100644 index 0000000000..59770cd1bd --- /dev/null +++ b/drivers/legrand-hid.h @@ -0,0 +1,30 @@ +/* legrand-hid.h - subdriver to monitor Legrand USB/HID devices with NUT + * + * Copyright (C) + * 2003 - 2009 Arnaud Quette + * 2005 - 2006 Peter Selinger + * 2008 - 2009 Arjen de Korte + * + * 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 LEGRAND_HID_H +#define LEGRAND_HID_H + +#include "usbhid-ups.h" + +extern subdriver_t legrand_subdriver; + +#endif /* LEGRAND_HID_H */ diff --git a/drivers/usbhid-ups.c b/drivers/usbhid-ups.c index bd58eb12de..0502324139 100644 --- a/drivers/usbhid-ups.c +++ b/drivers/usbhid-ups.c @@ -28,7 +28,7 @@ */ #define DRIVER_NAME "Generic HID driver" -#define DRIVER_VERSION "0.42" +#define DRIVER_VERSION "0.43" #include "main.h" #include "libhid.h" @@ -49,6 +49,7 @@ #include "tripplite-hid.h" #include "idowell-hid.h" #include "openups-hid.h" + #include "legrand-hid.h" #endif /* master list of avaiable subdrivers */ @@ -66,6 +67,7 @@ static subdriver_t *subdriver_list[] = { &tripplite_subdriver, &idowell_subdriver, &openups_subdriver, + &legrand_subdriver, #endif NULL }; From 0d1ae202921fad29630a7f9e0ea8b50d7c590918 Mon Sep 17 00:00:00 2001 From: Gabriele Taormina Date: Mon, 27 Aug 2018 11:28:44 +0200 Subject: [PATCH 04/16] metasys: fix handling of errors/special values As per protocol documentation, data is sent by the device with unsigned integers of 8, 16 or 32 bits, with errors/special values being: - for uint16: 0xFFFF and 0xFFFE, - for uint32: 0xFFFFFFFF. Alas, those values are (and, probably, have always been) actually reported in the documentation (mostly) as if their types were not unsigned (so, in decimal: -1, and -2) and the metasys driver used them like that, converting the values to non-fixed width signed types (int and long) and then expecting a value of -1 or -2 to signal a particular event, but this only happens on platforms where int is exactly 16 bits, and long 32 bits. So, leave values as unsigned types (adjusting all the printf formats accordingly), and use the right hex values, instead. --- drivers/metasys.c | 123 +++++++++++++++++++++++++++++----------------- 1 file changed, 79 insertions(+), 44 deletions(-) diff --git a/drivers/metasys.c b/drivers/metasys.c index 647de3c3ce..fe0eba7879 100644 --- a/drivers/metasys.c +++ b/drivers/metasys.c @@ -1,6 +1,8 @@ /* metasys.c - driver for Meta System UPS - Copyright (C) 2004 Fabio Di Niro + Copyright (C) + 2004 Fabio Di Niro + 2018 Gabriele Taormina , for Legrand 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 @@ -26,19 +28,21 @@ #include "serial.h" #define DRIVER_NAME "Metasystem UPS driver" -#define DRIVER_VERSION "0.07" +#define DRIVER_VERSION "0.08" /* driver description structure */ upsdrv_info_t upsdrv_info = { DRIVER_NAME, DRIVER_VERSION, - "Fabio Di Niro ", + "Fabio Di Niro " \ + "Gabriele Taormina ", DRV_STABLE, { NULL } }; /* Autorestart flag */ int autorestart = 0; +/* Nominal Active Power (W) of the UPS */ int nominal_power = 0; /* ups commands */ @@ -58,6 +62,11 @@ int nominal_power = 0; #define UPS_SET_BUZZER_MUTE 0x0d #define UPS_SET_BATTERY_TEST 0x0e +/* Protocol costants */ +#define OVERRANGE_VALUE 0xFFFF /* decimal -1 */ +#define NOT_AVAILABLE_VALUE 0xFFFE /* decimal -2 */ +#define NO_SHUTDOWN_RESTART 0xFFFFFFFF /* decimal -1 */ + static int instcmd(const char *cmdname, const char *extra); /* @@ -72,9 +81,9 @@ static int instcmd(const char *cmdname, const char *extra); The answer from the UPS have the same packet format and the first data byte is equal to the command that the ups is answering to */ -int get_word(unsigned char *buffer) { /* return an integer reading a word in the supplied buffer */ +unsigned int get_word(unsigned char *buffer) { /* return an integer reading a word in the supplied buffer */ unsigned char a, b; - int result; + unsigned int result; a = buffer[0]; b = buffer[1]; @@ -83,9 +92,9 @@ int get_word(unsigned char *buffer) { /* return an integer reading a word in th } -long int get_long(unsigned char *buffer) { /* return a long integer reading 4 bytes in the supplied buffer */ +unsigned long int get_long(unsigned char *buffer) { /* return a long integer reading 4 bytes in the supplied buffer */ unsigned char a, b, c, d; - long int result; + unsigned long int result; a=buffer[0]; b=buffer[1]; c=buffer[2]; @@ -566,12 +575,13 @@ void upsdrv_initinfo(void) void upsdrv_updateinfo(void) { - int res, int_num; + int res; + unsigned int int_num; #ifdef EXTRADATA int day, hour, minute; #endif float float_num; - long int long_num; + unsigned long int long_num; unsigned char my_answer[255]; /* GET Output data */ @@ -589,27 +599,35 @@ void upsdrv_updateinfo(void) dstate_setinfo("ups.load", "%s", "not available"); } #ifdef EXTRADATA - dstate_setinfo("output.power", "%d", int_num); + dstate_setinfo("output.power", "%u", int_num); #endif /* voltage */ int_num = get_word(&my_answer[3]); - if (int_num > 0) dstate_setinfo("output.voltage", "%d", int_num); - if (int_num == -1) dstate_setinfo("output.voltage", "%s", "overrange"); - if (int_num == -2) dstate_setinfo("output.voltage", "%s", "not available"); + if (int_num == OVERRANGE_VALUE) { + dstate_setinfo("output.voltage", "%s", "overrange"); + } else if (int_num == NOT_AVAILABLE_VALUE) { + dstate_setinfo("output.voltage", "%s", "not available"); + } else { + dstate_setinfo("output.voltage", "%u", int_num); + } /* current */ float_num = get_word(&my_answer[5]); - if (float_num == -1) dstate_setinfo("output.current", "%s", "overrange"); - if (float_num == -2) dstate_setinfo("output.current", "%s", "not available"); - if (float_num > 0) { + if (float_num == OVERRANGE_VALUE) { + dstate_setinfo("output.current", "%s", "overrange"); + } else if (float_num == NOT_AVAILABLE_VALUE) { + dstate_setinfo("output.current", "%s", "not available"); + } else { float_num = (float)(float_num/10); dstate_setinfo("output.current", "%2.2f", float_num); } #ifdef EXTRADATA /* peak current */ float_num = get_word(&my_answer[7]); - if (float_num == -1) dstate_setinfo("output.current.peak", "%s", "overrange"); - if (float_num == -2) dstate_setinfo("output.current.peak", "%s", "not available"); - if (float_num > 0) { + if (float_num == OVERRANGE_VALUE) { + dstate_setinfo("output.current.peak", "%s", "overrange"); + } else if (float_num == NOT_AVAILABLE_VALUE) { + dstate_setinfo("output.current.peak", "%s", "not available"); + } else { float_num = (float)(float_num/10); dstate_setinfo("output.current.peak", "%2.2f", float_num); } @@ -626,36 +644,47 @@ void upsdrv_updateinfo(void) #ifdef EXTRADATA /* Active power */ int_num = get_word(&my_answer[1]); - if (int_num > 0) dstate_setinfo("input.power", "%d", int_num); - if (int_num == -1) dstate_setinfo("input.power", "%s", "overrange"); - if (int_num == -2) dstate_setinfo("input.power", "%s", "not available"); + if (int_num == OVERRANGE_VALUE) { + dstate_setinfo("input.power", "%s", "overrange"); + } else if (int_num == NOT_AVAILABLE_VALUE) { + dstate_setinfo("input.power", "%s", "not available"); + } else { + dstate_setinfo("input.power", "%u", int_num); + } #endif /* voltage */ int_num = get_word(&my_answer[3]); - if (int_num > 0) dstate_setinfo("input.voltage", "%d", int_num); - if (int_num == -1) dstate_setinfo("input.voltage", "%s", "overrange"); - if (int_num == -2) dstate_setinfo("input.voltage", "%s", "not available"); + if (int_num == OVERRANGE_VALUE) { + dstate_setinfo("input.voltage", "%s", "overrange"); + } else if (int_num == NOT_AVAILABLE_VALUE) { + dstate_setinfo("input.voltage", "%s", "not available"); + } else { + dstate_setinfo("input.voltage", "%u", int_num); + } #ifdef EXTRADATA /* current */ float_num = get_word(&my_answer[5]); - if (float_num == -1) dstate_setinfo("input.current", "%s", "overrange"); - if (float_num == -2) dstate_setinfo("input.current", "%s", "not available"); - if (float_num > 0) { + if (float_num == OVERRANGE_VALUE) { + dstate_setinfo("input.current", "%s", "overrange"); + } else if (float_num == NOT_AVAILABLE_VALUE) { + dstate_setinfo("input.current", "%s", "not available"); + } else { float_num = (float)(float_num/10); dstate_setinfo("input.current", "%2.2f", float_num); } /* peak current */ float_num = get_word(&my_answer[7]); - if (float_num == -1) dstate_setinfo("input.current.peak", "%s", "overrange"); - if (float_num == -2) dstate_setinfo("input.current.peak", "%s", "not available"); - if (float_num > 0) { + if (float_num == OVERRANGE_VALUE) { + dstate_setinfo("input.current.peak", "%s", "overrange"); + } else if (float_num == NOT_AVAILABLE_VALUE) { + dstate_setinfo("input.current.peak", "%s", "not available"); + } else if (float_num > 0) { float_num = (float)(float_num/10); dstate_setinfo("input.current.peak", "%2.2f", float_num); } #endif } - /* GET Battery data */ res = command_read_sequence(UPS_BATTERY_DATA, my_answer); if (res < 0) { @@ -693,7 +722,7 @@ void upsdrv_updateinfo(void) long_num -= (long)(hour*3600); minute = (int)(long_num / 60); long_num -= (minute*60); - dstate_setinfo("ups.total.runtime", "%d days %dh %dm %lds", day, hour, minute, long_num); + dstate_setinfo("ups.total.runtime", "%d days %dh %dm %lus", day, hour, minute, long_num); /* ups inverter runtime */ long_num = get_long(&my_answer[5]); @@ -703,19 +732,25 @@ void upsdrv_updateinfo(void) long_num -= (long)(hour*3600); minute = (int)(long_num / 60); long_num -= (minute*60); - dstate_setinfo("ups.inverter.runtime", "%d days %dh %dm %lds", day, hour, minute, long_num); + dstate_setinfo("ups.inverter.runtime", "%d days %dh %dm %lus", day, hour, minute, long_num); /* ups inverter interventions */ - dstate_setinfo("ups.inverter.interventions", "%d", get_word(&my_answer[9])); + dstate_setinfo("ups.inverter.interventions", "%u", get_word(&my_answer[9])); /* battery full discharges */ - dstate_setinfo("battery.full.discharges", "%d", get_word(&my_answer[11])); + dstate_setinfo("battery.full.discharges", "%u", get_word(&my_answer[11])); /* ups bypass / stabilizer interventions */ int_num = get_word(&my_answer[13]); - if (int_num == -2) dstate_setinfo("ups.bypass.interventions", "%s", "not avaliable"); - if (int_num >= 0) dstate_setinfo("ups.bypass.interventions", "%d", int_num); + if (int_num == NOT_AVAILABLE_VALUE) { + dstate_setinfo("ups.bypass.interventions", "%s", "not avaliable"); + } else { + dstate_setinfo("ups.bypass.interventions", "%u", int_num); + } /* ups overheatings */ int_num = get_word(&my_answer[15]); - if (int_num == -2) dstate_setinfo("ups.overheatings", "%s", "not avalilable"); - if (int_num >= 0) dstate_setinfo("ups.overheatings", "%d", int_num); + if (int_num == NOT_AVAILABLE_VALUE) { + dstate_setinfo("ups.overheatings", "%s", "not avalilable"); + } else { + dstate_setinfo("ups.overheatings", "%u", int_num); + } } #endif @@ -737,17 +772,17 @@ void upsdrv_updateinfo(void) } else { /* time remaining to shutdown */ long_num = get_long(&my_answer[1]); - if (long_num == -1) { + if (long_num == NO_SHUTDOWN_RESTART) { dstate_setinfo("ups.delay.shutdown", "%d", 120); } else { - dstate_setinfo("ups.delay.shutdown", "%ld", long_num); + dstate_setinfo("ups.delay.shutdown", "%lu", long_num); } /* time remaining to restart */ long_num = get_long(&my_answer[5]); - if (long_num == -1) { + if (long_num == NO_SHUTDOWN_RESTART) { dstate_setinfo("ups.delay.start", "%d", 0); } else { - dstate_setinfo("ups.delay.start", "%ld", long_num); + dstate_setinfo("ups.delay.start", "%lu", long_num); } } From 264fcb43173c2c1cbf2860ac00d4edabf108dee9 Mon Sep 17 00:00:00 2001 From: Gabriele Taormina Date: Mon, 27 Aug 2018 11:28:44 +0200 Subject: [PATCH 05/16] metasys: for bypass mode, call status_set() with "BYPASS", not "BY" --- drivers/metasys.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/metasys.c b/drivers/metasys.c index fe0eba7879..c256eccd9d 100644 --- a/drivers/metasys.c +++ b/drivers/metasys.c @@ -28,7 +28,7 @@ #include "serial.h" #define DRIVER_NAME "Metasystem UPS driver" -#define DRIVER_VERSION "0.08" +#define DRIVER_VERSION "0.09" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -814,7 +814,7 @@ void upsdrv_updateinfo(void) break; case 0x03: /* bypass engaged */ case 0x04: /* manual bypass engaged */ - status_set("BY"); + status_set("BYPASS"); break; default: printf("status unknown \n"); From 45ef76905d772d4bffe905ccb34e03cebdc2b2b0 Mon Sep 17 00:00:00 2001 From: Gabriele Taormina Date: Mon, 27 Aug 2018 11:28:44 +0200 Subject: [PATCH 06/16] metasys: treat any '\0' in the serial number as a '0' --- drivers/metasys.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/metasys.c b/drivers/metasys.c index c256eccd9d..2ebc48d262 100644 --- a/drivers/metasys.c +++ b/drivers/metasys.c @@ -28,7 +28,7 @@ #include "serial.h" #define DRIVER_NAME "Metasystem UPS driver" -#define DRIVER_VERSION "0.09" +#define DRIVER_VERSION "0.10" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -551,6 +551,10 @@ void upsdrv_initinfo(void) memcpy(serial, my_answer + 7, res - 7); /* serial number start from the 8th byte */ serial[12]=0; /* terminate string */ + for (i = 0; i < 12; i++) { + if (serial[i] == 0) + serial[i] = '0'; + } dstate_setinfo("ups.serial", "%s", serial); /* get the ups firmware. The major number is in the 5th byte, the minor is in the 6th */ From a930788dd860a8d431bf79ff27f5911bb5f97ece Mon Sep 17 00:00:00 2001 From: Gabriele Taormina Date: Mon, 27 Aug 2018 11:28:44 +0200 Subject: [PATCH 07/16] metasys: support some newer Meta System and Legrand devices Add to the metasys driver a few new models (Meta System DHEA, and others now branded Legrand) speaking the Meta System UPS protocol. --- data/driver.list.in | 22 +++++++++++++++++++ drivers/metasys.c | 52 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/data/driver.list.in b/data/driver.list.in index e7e91d1f71..188cedb4ee 100644 --- a/data/driver.list.in +++ b/data/driver.list.in @@ -520,6 +520,26 @@ "Legrand" "ups" "2" "Keor Multiplug" "" "nutdrv_qx" "Legrand" "ups" "2" "Keor PDU" "USB" "usbhid-ups" "Legrand" "ups" "2" "Keor SP" "USB" "usbhid-ups" +"Legrand" "ups" "4" "Megaline 1250" "Serial" "metasys" +"Legrand" "ups" "4" "Megaline 2500" "Serial" "metasys" +"Legrand" "ups" "4" "Megaline 3750" "Serial" "metasys" +"Legrand" "ups" "4" "Megaline 5000" "Serial" "metasys" +"Legrand" "ups" "4" "Megaline 5000 /2" "Serial" "metasys" +"Legrand" "ups" "4" "Megaline 6250 /2" "Serial" "metasys" +"Legrand" "ups" "4" "Megaline 7500 /2" "Serial" "metasys" +"Legrand" "ups" "4" "Megaline 8750 /2" "Serial" "metasys" +"Legrand" "ups" "4" "Megaline 10000 /2" "Serial" "metasys" +"Legrand" "ups" "4" "WHAD 800" "Serial" "metasys" +"Legrand" "ups" "4" "WHAD 1000" "Serial" "metasys" +"Legrand" "ups" "4" "WHAD 1250" "Serial" "metasys" +"Legrand" "ups" "4" "WHAD 1500" "Serial" "metasys" +"Legrand" "ups" "4" "WHAD 2000" "Serial" "metasys" +"Legrand" "ups" "4" "WHAD 2500" "Serial" "metasys" +"Legrand" "ups" "4" "WHAD CAB 1250" "Serial" "metasys" +"Legrand" "ups" "4" "WHAD CAB 2500" "Serial" "metasys" +"Legrand" "ups" "4" "WHAD HE 800" "Serial" "metasys" +"Legrand" "ups" "4" "WHAD HE 1000" "Serial" "metasys" +"Legrand" "ups" "4" "WHAD HE 1500" "Serial" "metasys" "Lestar" "ups" "2" "MD-800E" "" "blazer_ser" @@ -586,6 +606,8 @@ "Meta System" "ups" "1" "Megaline 7500" "" "metasys" "Meta System" "ups" "1" "Megaline 8750" "" "metasys" "Meta System" "ups" "1" "Megaline 10000" "" "metasys" +"Meta System" "ups" "4" "DHEA 1000" "Serial" "metasys" +"Meta System" "ups" "4" "DHEA 1500" "Serial" "metasys" "MGE Office Protection Systems" "ups" "5" "Protection Center 500/675 VA" "USB" "usbhid-ups" "MGE Office Protection Systems" "ups" "5" "Protection Station 500/650/800 VA" "USB" "usbhid-ups" diff --git a/drivers/metasys.c b/drivers/metasys.c index 2ebc48d262..d7062d45f7 100644 --- a/drivers/metasys.c +++ b/drivers/metasys.c @@ -1,4 +1,4 @@ -/* metasys.c - driver for Meta System UPS +/* metasys.c - driver for Meta System and Legrand devices Copyright (C) 2004 Fabio Di Niro @@ -27,8 +27,8 @@ #include "main.h" #include "serial.h" -#define DRIVER_NAME "Metasystem UPS driver" -#define DRIVER_VERSION "0.10" +#define DRIVER_NAME "Meta System / Legrand UPS driver" +#define DRIVER_VERSION "0.11" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -330,7 +330,7 @@ void upsdrv_initinfo(void) if (res < 0) fatal_with_errno(EXIT_FAILURE, "Could not communicate with the ups"); /* the manufacturer is hard coded into the driver, the model type is in the second byte of the answer, the third byte identifies the model version */ - dstate_setinfo("ups.mfr", "Meta System"); + dstate_setinfo("ups.mfr", "Meta System / Legrand"); i = my_answer[1] * 10 + my_answer[2]; switch (i) { case 11: @@ -541,6 +541,50 @@ void upsdrv_initinfo(void) dstate_setinfo("ups.model", "%s", "Megaline 10000 / 2"); nominal_power = 7000; break; + case 171: + dstate_setinfo("ups.model", "%s", "WHAD 800"); + nominal_power = 560; + break; + case 181: + dstate_setinfo("ups.model", "%s", "WHAD 1000"); + nominal_power = 700; + break; + case 191: + dstate_setinfo("ups.model", "%s", "WHAD 1500"); + nominal_power = 1050; + break; + case 201: + dstate_setinfo("ups.model", "%s", "DHEA 1000"); + nominal_power = 700; + break; + case 211: + dstate_setinfo("ups.model", "%s", "DHEA 1500"); + nominal_power = 1050; + break; + case 272: + dstate_setinfo("ups.model", "%s", "WHAD 2000"); + nominal_power = 1400; + break; + case 281: + dstate_setinfo("ups.model", "%s", "WHAD / WHAD CAB 1250"); + nominal_power = 875; + break; + case 282: + dstate_setinfo("ups.model", "%s", "WHAD / WHAD CAB 2500"); + nominal_power = 1750; + break; + case 311: + dstate_setinfo("ups.model", "%s", "WHAD HE 800"); + nominal_power = 800; + break; + case 321: + dstate_setinfo("ups.model", "%s", "WHAD HE 1000"); + nominal_power = 1000; + break; + case 331: + dstate_setinfo("ups.model", "%s", "WHAD HE 1500"); + nominal_power = 1500; + break; default: fatal_with_errno(EXIT_FAILURE, "Unknown UPS"); From 227e6bd19c7d56e4f915d13e84ed4c2999d239d4 Mon Sep 17 00:00:00 2001 From: Daniele Pezzini Date: Wed, 5 Sep 2018 02:12:52 +0200 Subject: [PATCH 08/16] metasys: remove useless initialisation of vars --- drivers/metasys.c | 59 +---------------------------------------------- 1 file changed, 1 insertion(+), 58 deletions(-) diff --git a/drivers/metasys.c b/drivers/metasys.c index d7062d45f7..cd3a4ff6bc 100644 --- a/drivers/metasys.c +++ b/drivers/metasys.c @@ -28,7 +28,7 @@ #include "serial.h" #define DRIVER_NAME "Meta System / Legrand UPS driver" -#define DRIVER_VERSION "0.11" +#define DRIVER_VERSION "0.12" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -268,63 +268,6 @@ void upsdrv_initinfo(void) char serial[13]; int res, i; - /* Initial setup of variables */ -#ifdef EXTRADATA - dstate_setinfo("output.power", "%d", -1); - dstate_setflags("output.power", ST_FLAG_RW); -#endif - dstate_setinfo("output.voltage", "%d", -1); - dstate_setflags("output.voltage", ST_FLAG_RW); - dstate_setinfo("output.current", "%d", -1); - dstate_setflags("output.current", ST_FLAG_RW); -#ifdef EXTRADATA - dstate_setinfo("output.current.peak", "%2.2f", -1); - dstate_setflags("output.current.peak", ST_FLAG_RW); - dstate_setinfo("input.power", "%d", -1); - dstate_setflags("input.power", ST_FLAG_RW); -#endif - dstate_setinfo("input.voltage", "%d", -1); - dstate_setflags("input.voltage", ST_FLAG_RW); -#ifdef EXTRADATA - dstate_setinfo("input.current", "%2.2f", -1); - dstate_setflags("input.current", ST_FLAG_RW); - dstate_setinfo("input.current.peak", "%2.2f", -1); - dstate_setflags("input.current.peak", ST_FLAG_RW); -#endif - dstate_setinfo("battery.voltage", "%d", -1); - dstate_setflags("battery.voltage", ST_FLAG_RW); -#ifdef EXTRADATA - dstate_setinfo("battery.voltage.low", "%2.2f", -1); - dstate_setflags("battery.voltage.low", ST_FLAG_RW); - dstate_setinfo("battery.voltage.exhaust", "%2.2f", -1); - dstate_setflags("battery.voltage.exhaust", ST_FLAG_RW); - dstate_setinfo("ups.total.runtime", "retrieving..."); - dstate_setflags("ups.total.runtime", ST_FLAG_STRING | ST_FLAG_RW); - dstate_setaux("ups.total.runtime", 20); - dstate_setinfo("ups.inverter.runtime", "retrieving..."); - dstate_setflags("ups.inverter.runtime", ST_FLAG_STRING | ST_FLAG_RW); - dstate_setaux("ups.inverter.runtime", 20); - dstate_setinfo("ups.inverter.interventions", "%d", -1); - dstate_setflags("ups.inverter.interventions", ST_FLAG_RW); - dstate_setinfo("battery.full.discharges", "%d", -1); - dstate_setflags("battery.full.discharges", ST_FLAG_RW); - dstate_setinfo("ups.bypass.interventions", "%d", -1); - dstate_setflags("ups.bypass.interventions", ST_FLAG_RW); - dstate_setinfo("ups.overheatings", "%d", -1); - dstate_setflags("ups.overheatings", ST_FLAG_RW); -#endif - dstate_setinfo("ups.load", "%d", -1); - dstate_setflags("ups.load", ST_FLAG_RW); - dstate_setinfo("ups.delay.shutdown", "%d", -1); - dstate_setflags("ups.delay.shutdown", ST_FLAG_RW); - dstate_setinfo("ups.delay.start", "%d", -1); - dstate_setflags("ups.delay.start", ST_FLAG_RW); - dstate_setinfo("ups.temperature", "%d", -1); - dstate_setflags("ups.temperature", ST_FLAG_RW); - dstate_setinfo("ups.test.result", "not yet done..."); - dstate_setflags("ups.test.result", ST_FLAG_STRING | ST_FLAG_RW); - dstate_setaux("ups.test.result", 20); - /* UPS INFO READ */ res = command_read_sequence(UPS_INFO, my_answer); if (res < 0) fatal_with_errno(EXIT_FAILURE, "Could not communicate with the ups"); From 367eae6634a5d8e2bb6d0d054c9f89e920f2ffc4 Mon Sep 17 00:00:00 2001 From: Gabriele Taormina Date: Thu, 6 Sep 2018 00:02:10 +0200 Subject: [PATCH 09/16] metasys: retrieve also battery charge and runtime The added command is only supported by devices with an 'id code' >= 14, while for other, older, models the command is not enabled by default, and the user has to perform a specific procedure via serial to enable it (where supported). --- drivers/metasys.c | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/drivers/metasys.c b/drivers/metasys.c index cd3a4ff6bc..f5092bd666 100644 --- a/drivers/metasys.c +++ b/drivers/metasys.c @@ -28,7 +28,7 @@ #include "serial.h" #define DRIVER_NAME "Meta System / Legrand UPS driver" -#define DRIVER_VERSION "0.12" +#define DRIVER_VERSION "0.13" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -45,12 +45,16 @@ int autorestart = 0; /* Nominal Active Power (W) of the UPS */ int nominal_power = 0; +/* Battery SOC Data discrimation variable */ +static int batt_soc = 0; + /* ups commands */ #define UPS_INFO 0x00 #define UPS_OUTPUT_DATA 0x01 #define UPS_INPUT_DATA 0x02 #define UPS_STATUS 0x03 #define UPS_BATTERY_DATA 0x04 +#define UPS_BATTERY_SOC_DATA 0x25 #define UPS_HISTORY_DATA 0x05 #define UPS_GET_SCHEDULING 0x06 #define UPS_EVENT_LIST 0x07 @@ -451,82 +455,102 @@ void upsdrv_initinfo(void) case 141: dstate_setinfo("ups.model", "%s", "Megaline 1250"); nominal_power = 875; + batt_soc = 1; break; case 142: dstate_setinfo("ups.model", "%s", "Megaline 2500"); nominal_power = 1750; + batt_soc = 1; break; case 143: dstate_setinfo("ups.model", "%s", "Megaline 3750"); nominal_power = 2625; + batt_soc = 1; break; case 144: dstate_setinfo("ups.model", "%s", "Megaline 5000"); nominal_power = 3500; + batt_soc = 1; break; case 154: dstate_setinfo("ups.model", "%s", "Megaline 5000 / 2"); nominal_power = 3500; + batt_soc = 1; break; case 155: dstate_setinfo("ups.model", "%s", "Megaline 6250 / 2"); nominal_power = 4375; + batt_soc = 1; break; case 156: dstate_setinfo("ups.model", "%s", "Megaline 7500 / 2"); nominal_power = 5250; + batt_soc = 1; break; case 157: dstate_setinfo("ups.model", "%s", "Megaline 8750 / 2"); nominal_power = 6125; + batt_soc = 1; break; case 158: dstate_setinfo("ups.model", "%s", "Megaline 10000 / 2"); nominal_power = 7000; + batt_soc = 1; break; case 171: dstate_setinfo("ups.model", "%s", "WHAD 800"); nominal_power = 560; + batt_soc = 1; break; case 181: dstate_setinfo("ups.model", "%s", "WHAD 1000"); nominal_power = 700; + batt_soc = 1; break; case 191: dstate_setinfo("ups.model", "%s", "WHAD 1500"); nominal_power = 1050; + batt_soc = 1; break; case 201: dstate_setinfo("ups.model", "%s", "DHEA 1000"); nominal_power = 700; + batt_soc = 1; break; case 211: dstate_setinfo("ups.model", "%s", "DHEA 1500"); nominal_power = 1050; + batt_soc = 1; break; case 272: dstate_setinfo("ups.model", "%s", "WHAD 2000"); nominal_power = 1400; + batt_soc = 1; break; case 281: dstate_setinfo("ups.model", "%s", "WHAD / WHAD CAB 1250"); nominal_power = 875; + batt_soc = 1; break; case 282: dstate_setinfo("ups.model", "%s", "WHAD / WHAD CAB 2500"); nominal_power = 1750; + batt_soc = 1; break; case 311: dstate_setinfo("ups.model", "%s", "WHAD HE 800"); nominal_power = 800; + batt_soc = 1; break; case 321: dstate_setinfo("ups.model", "%s", "WHAD HE 1000"); nominal_power = 1000; + batt_soc = 1; break; case 331: dstate_setinfo("ups.model", "%s", "WHAD HE 1500"); nominal_power = 1500; + batt_soc = 1; break; default: @@ -675,7 +699,21 @@ void upsdrv_updateinfo(void) } #endif } - + + /* GET Battery SOC data */ + if (batt_soc) { + res = command_read_sequence(UPS_BATTERY_SOC_DATA, my_answer); + if (res < 0) { + printf("Could not communicate with the UPS"); + dstate_datastale(); + } else { + int_num = get_word(&my_answer[2]); + dstate_setinfo("battery.runtime", "%u", int_num); + int_num = my_answer[4]; + dstate_setinfo("battery.charge", "%u", int_num); + } + } + /* GET Battery data */ res = command_read_sequence(UPS_BATTERY_DATA, my_answer); if (res < 0) { From 4e675816019d26eddf151058670a4000b5f0a60e Mon Sep 17 00:00:00 2001 From: Daniele Pezzini Date: Thu, 6 Sep 2018 00:06:11 +0200 Subject: [PATCH 10/16] HCL: update support level of some Meta System devices As per the provided protocol: see nut-website's 570c1e5e9f1fb4f82abef9359897b5159e88fc50 Also, fix name of "HF Line /2". --- data/driver.list.in | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/data/driver.list.in b/data/driver.list.in index 188cedb4ee..311f6cfccb 100644 --- a/data/driver.list.in +++ b/data/driver.list.in @@ -565,7 +565,7 @@ "Mecer" "ups" "2" "ME-2000" "" "blazer_ser" "Meta System" "ups" "1" "HF Line" "1..4 boards" "metasys" -"Meta System" "ups" "1" "HF Line \/2" "5..8 boards" "metasys" +"Meta System" "ups" "1" "HF Line /2" "5..8 boards" "metasys" "Meta System" "ups" "1" "HF Millennium 810" "" "metasys" "Meta System" "ups" "1" "HF Millennium 820" "" "metasys" "Meta System" "ups" "1" "HF TOP Line 910" "" "metasys" @@ -592,20 +592,20 @@ "Meta System" "ups" "1" "ECO 516" "" "metasys" "Meta System" "ups" "1" "ECO 519" "" "metasys" "Meta System" "ups" "1" "ECO 522" "" "metasys" -"Meta System" "ups" "1" "ally HF 800" "" "metasys" -"Meta System" "ups" "1" "ally HF 1000" "" "metasys" -"Meta System" "ups" "1" "ally HF 1250" "" "metasys" -"Meta System" "ups" "1" "ally HF 1600" "" "metasys" -"Meta System" "ups" "1" "ally HF 2000" "" "metasys" -"Meta System" "ups" "1" "ally HF 2500" "" "metasys" -"Meta System" "ups" "1" "Megaline 1250" "" "metasys" -"Meta System" "ups" "1" "Megaline 2500" "" "metasys" -"Meta System" "ups" "1" "Megaline 3750" "" "metasys" -"Meta System" "ups" "1" "Megaline 5000" "" "metasys" -"Meta System" "ups" "1" "Megaline 6250" "" "metasys" -"Meta System" "ups" "1" "Megaline 7500" "" "metasys" -"Meta System" "ups" "1" "Megaline 8750" "" "metasys" -"Meta System" "ups" "1" "Megaline 10000" "" "metasys" +"Meta System" "ups" "4" "ally HF 800" "" "metasys" +"Meta System" "ups" "4" "ally HF 1000" "" "metasys" +"Meta System" "ups" "4" "ally HF 1250" "" "metasys" +"Meta System" "ups" "4" "ally HF 1600" "" "metasys" +"Meta System" "ups" "4" "ally HF 2000" "" "metasys" +"Meta System" "ups" "4" "ally HF 2500" "" "metasys" +"Meta System" "ups" "4" "Megaline 1250" "" "metasys" +"Meta System" "ups" "4" "Megaline 2500" "" "metasys" +"Meta System" "ups" "4" "Megaline 3750" "" "metasys" +"Meta System" "ups" "4" "Megaline 5000" "" "metasys" +"Meta System" "ups" "4" "Megaline 6250" "" "metasys" +"Meta System" "ups" "4" "Megaline 7500" "" "metasys" +"Meta System" "ups" "4" "Megaline 8750" "" "metasys" +"Meta System" "ups" "4" "Megaline 10000" "" "metasys" "Meta System" "ups" "4" "DHEA 1000" "Serial" "metasys" "Meta System" "ups" "4" "DHEA 1500" "Serial" "metasys" From 632deb4221f4f782fd13ebbbbf0a885b17d07ff1 Mon Sep 17 00:00:00 2001 From: Daniele Pezzini Date: Thu, 6 Sep 2018 00:08:31 +0200 Subject: [PATCH 11/16] HCL: add some Legrand devices supported by nutdrv_qx Also, specify that Legrand Multiplug is USB-only. --- data/driver.list.in | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/data/driver.list.in b/data/driver.list.in index 311f6cfccb..38bd0b6720 100644 --- a/data/driver.list.in +++ b/data/driver.list.in @@ -517,9 +517,15 @@ "Legrand" "ups" "2" "Daker DK" "USB" "nutdrv_qx" "Legrand" "ups" "2" "Daker DK Plus" "Serial" "nutdrv_qx" "Legrand" "ups" "2" "Daker DK Plus" "USB" "nutdrv_qx" -"Legrand" "ups" "2" "Keor Multiplug" "" "nutdrv_qx" +"Legrand" "ups" "2" "Keor Line RT" "Serial" "nutdrv_qx" +"Legrand" "ups" "2" "Keor Line RT" "USB" "nutdrv_qx" +"Legrand" "ups" "2" "Keor LP" "Serial" "nutdrv_qx" +"Legrand" "ups" "2" "Keor Multiplug" "USB" "nutdrv_qx" +"Legrand" "ups" "2" "Keor S" "Serial" "nutdrv_qx" +"Legrand" "ups" "2" "Keor S" "USB" "nutdrv_qx" "Legrand" "ups" "2" "Keor PDU" "USB" "usbhid-ups" "Legrand" "ups" "2" "Keor SP" "USB" "usbhid-ups" +"Legrand" "ups" "2" "Keor SPX" "USB" "nutdrv_qx" "Legrand" "ups" "4" "Megaline 1250" "Serial" "metasys" "Legrand" "ups" "4" "Megaline 2500" "Serial" "metasys" "Legrand" "ups" "4" "Megaline 3750" "Serial" "metasys" @@ -529,6 +535,10 @@ "Legrand" "ups" "4" "Megaline 7500 /2" "Serial" "metasys" "Legrand" "ups" "4" "Megaline 8750 /2" "Serial" "metasys" "Legrand" "ups" "4" "Megaline 10000 /2" "Serial" "metasys" +"Legrand" "ups" "2" "Niky" "Serial" "nutdrv_qx" +"Legrand" "ups" "2" "Niky" "USB" "nutdrv_qx" +"Legrand" "ups" "2" "Niky S" "Serial" "nutdrv_qx" +"Legrand" "ups" "2" "Niky S" "USB" "nutdrv_qx" "Legrand" "ups" "4" "WHAD 800" "Serial" "metasys" "Legrand" "ups" "4" "WHAD 1000" "Serial" "metasys" "Legrand" "ups" "4" "WHAD 1250" "Serial" "metasys" From 085c700db5fb888a0474f0bf616cdc194f8ba2d3 Mon Sep 17 00:00:00 2001 From: Daniele Pezzini Date: Mon, 17 Sep 2018 01:16:24 +0200 Subject: [PATCH 12/16] HCL: update support level of some Meta System devices As per the provided protocol: see nut-website's 66d30c90401ff7929b25e5a87f9299b728f861ea --- data/driver.list.in | 56 ++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/data/driver.list.in b/data/driver.list.in index 38bd0b6720..a036bc0c8e 100644 --- a/data/driver.list.in +++ b/data/driver.list.in @@ -574,34 +574,34 @@ "Mecer" "ups" "2" "ME-1000-WTU" "USB" "nutdrv_qx" # http://www.comx-computers.co.za/download/mecer/ME-1000-WTU.pdf "Mecer" "ups" "2" "ME-2000" "" "blazer_ser" -"Meta System" "ups" "1" "HF Line" "1..4 boards" "metasys" -"Meta System" "ups" "1" "HF Line /2" "5..8 boards" "metasys" -"Meta System" "ups" "1" "HF Millennium 810" "" "metasys" -"Meta System" "ups" "1" "HF Millennium 820" "" "metasys" -"Meta System" "ups" "1" "HF TOP Line 910" "" "metasys" -"Meta System" "ups" "1" "HF TOP Line 920" "" "metasys" -"Meta System" "ups" "1" "HF TOP Line 930" "" "metasys" -"Meta System" "ups" "1" "HF TOP Line 940" "" "metasys" -"Meta System" "ups" "1" "HF TOP Line 950" "" "metasys" -"Meta System" "ups" "1" "HF TOP Line 960" "" "metasys" -"Meta System" "ups" "1" "HF TOP Line 970" "" "metasys" -"Meta System" "ups" "1" "HF TOP Line 980" "" "metasys" -"Meta System" "ups" "1" "ECO Network 750" "" "metasys" -"Meta System" "ups" "1" "ECO Network 1000" "" "metasys" -"Meta System" "ups" "1" "ECO Network 1050" "" "metasys" -"Meta System" "ups" "1" "ECO Network 1500" "" "metasys" -"Meta System" "ups" "1" "ECO Network 1800" "" "metasys" -"Meta System" "ups" "1" "ECO Network 2000" "" "metasys" -"Meta System" "ups" "1" "ECO Network 2100" "" "metasys" -"Meta System" "ups" "1" "ECO Network 2500" "" "metasys" -"Meta System" "ups" "1" "ECO Network 3000" "" "metasys" -"Meta System" "ups" "1" "ECO 305" "" "metasys" -"Meta System" "ups" "1" "ECO 308" "" "metasys" -"Meta System" "ups" "1" "ECO 311" "" "metasys" -"Meta System" "ups" "1" "ECO 511" "" "metasys" -"Meta System" "ups" "1" "ECO 516" "" "metasys" -"Meta System" "ups" "1" "ECO 519" "" "metasys" -"Meta System" "ups" "1" "ECO 522" "" "metasys" +"Meta System" "ups" "4" "HF Line" "1..4 boards" "metasys" +"Meta System" "ups" "4" "HF Line /2" "5..8 boards" "metasys" +"Meta System" "ups" "4" "HF Millennium 810" "" "metasys" +"Meta System" "ups" "4" "HF Millennium 820" "" "metasys" +"Meta System" "ups" "4" "HF TOP Line 910" "" "metasys" +"Meta System" "ups" "4" "HF TOP Line 920" "" "metasys" +"Meta System" "ups" "4" "HF TOP Line 930" "" "metasys" +"Meta System" "ups" "4" "HF TOP Line 940" "" "metasys" +"Meta System" "ups" "4" "HF TOP Line 950" "" "metasys" +"Meta System" "ups" "4" "HF TOP Line 960" "" "metasys" +"Meta System" "ups" "4" "HF TOP Line 970" "" "metasys" +"Meta System" "ups" "4" "HF TOP Line 980" "" "metasys" +"Meta System" "ups" "4" "ECO Network 750" "" "metasys" +"Meta System" "ups" "4" "ECO Network 1000" "" "metasys" +"Meta System" "ups" "4" "ECO Network 1050" "" "metasys" +"Meta System" "ups" "4" "ECO Network 1500" "" "metasys" +"Meta System" "ups" "4" "ECO Network 1800" "" "metasys" +"Meta System" "ups" "4" "ECO Network 2000" "" "metasys" +"Meta System" "ups" "4" "ECO Network 2100" "" "metasys" +"Meta System" "ups" "4" "ECO Network 2500" "" "metasys" +"Meta System" "ups" "4" "ECO Network 3000" "" "metasys" +"Meta System" "ups" "4" "ECO 305" "" "metasys" +"Meta System" "ups" "4" "ECO 308" "" "metasys" +"Meta System" "ups" "4" "ECO 311" "" "metasys" +"Meta System" "ups" "4" "ECO 511" "" "metasys" +"Meta System" "ups" "4" "ECO 516" "" "metasys" +"Meta System" "ups" "4" "ECO 519" "" "metasys" +"Meta System" "ups" "4" "ECO 522" "" "metasys" "Meta System" "ups" "4" "ally HF 800" "" "metasys" "Meta System" "ups" "4" "ally HF 1000" "" "metasys" "Meta System" "ups" "4" "ally HF 1250" "" "metasys" From ce6cff5d6dc398edc72537adb371fea9ba6365cc Mon Sep 17 00:00:00 2001 From: root Date: Wed, 18 Aug 2021 22:23:33 +0200 Subject: [PATCH 13/16] legrand hid rules update --- scripts/upower/95-upower-hid.rules | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/upower/95-upower-hid.rules b/scripts/upower/95-upower-hid.rules index 4f9c379a43..3183e17804 100644 --- a/scripts/upower/95-upower-hid.rules +++ b/scripts/upower/95-upower-hid.rules @@ -34,6 +34,7 @@ ATTRS{idVendor}=="0764", ENV{UPOWER_VENDOR}="Cyber Power Systems" ATTRS{idVendor}=="09ae", ENV{UPOWER_VENDOR}="TrippLite" ATTRS{idVendor}=="0d9f", ENV{UPOWER_VENDOR}="PowerCOM" ATTRS{idVendor}=="10af", ENV{UPOWER_VENDOR}="Liebert" +ATTRS{idVendor}=="1cb0", ENV{UPOWER_VENDOR}="Legrand" ATTRS{idVendor}=="2b2d", ENV{UPOWER_VENDOR}="AEG" # Hewlett Packard @@ -144,6 +145,10 @@ ATTRS{idVendor}=="10af", ATTRS{idProduct}=="0001", ENV{UPOWER_BATTERY_TYPE}="ups ATTRS{idVendor}=="10af", ATTRS{idProduct}=="0004", ENV{UPOWER_BATTERY_TYPE}="ups" ATTRS{idVendor}=="10af", ATTRS{idProduct}=="0008", ENV{UPOWER_BATTERY_TYPE}="ups" +# Legrand +ATTRS{idVendor}=="1cb0", ATTRS{idProduct}=="0032", ENV{UPOWER_BATTERY_TYPE}="ups" +ATTRS{idVendor}=="1cb0", ATTRS{idProduct}=="0038", ENV{UPOWER_BATTERY_TYPE}="ups" + # AEG ATTRS{idVendor}=="2b2d", ATTRS{idProduct}=="ffff", ENV{UPOWER_BATTERY_TYPE}="ups" From ac58d557d83fcaa7623454f351d92e25c66dab57 Mon Sep 17 00:00:00 2001 From: Stephan Blecher <_github@blecher.at> Date: Thu, 19 Aug 2021 20:10:02 +0200 Subject: [PATCH 14/16] issue-616: legrand 600 fix input voltage --- drivers/legrand-hid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/legrand-hid.c b/drivers/legrand-hid.c index 67ad91521e..63a2cf11c8 100644 --- a/drivers/legrand-hid.c +++ b/drivers/legrand-hid.c @@ -117,7 +117,7 @@ static info_lkp_t legrand_times10M_info[] = { static hid_info_t legrand_hid2nut[] = { /* Input Data */ - { "input.voltage", 0, 0, "UPS.Input.Voltage", NULL, "%.0f", 0, legrand_times10_info }, + { "input.voltage", 0, 0, "UPS.Input.Voltage", NULL, "%.0f", 0, NULL }, { "input.voltage", 0, 0, "UPS.PowerConverter.Input.Voltage", NULL, "%.0f", 0, legrand_times1M_info }, { "input.transfer.high", 0, 0, "UPS.Input.HighVoltageTransfer", NULL, "%.0f", HU_FLAG_STATIC, NULL }, { "input.transfer.high", 0, 0, "UPS.PowerConverter.Output.HighVoltageTransfer", NULL, "%.0f", HU_FLAG_STATIC, legrand_times10_info }, From ba7656d0d4a7e1039d3700e311b3e430fa4f1623 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Tue, 21 Sep 2021 22:24:37 +0200 Subject: [PATCH 15/16] Update legrand-hid.c Address build warnings from CI --- drivers/legrand-hid.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/legrand-hid.c b/drivers/legrand-hid.c index 63a2cf11c8..b251e63e78 100644 --- a/drivers/legrand-hid.c +++ b/drivers/legrand-hid.c @@ -22,6 +22,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "common.h" #include "usbhid-ups.h" #include "legrand-hid.h" #include "main.h" @@ -36,6 +37,8 @@ static void *disable_interrupt_pipe(USBDevice_t *device) { + NUT_UNUSED_VARIABLE(device); + if (!use_interrupt_pipe) return NULL; use_interrupt_pipe = FALSE; @@ -75,7 +78,8 @@ static const char *legrand_times10(double value) } static info_lkp_t legrand_times10_info[] = { - { 0, NULL, legrand_times10 } + { 0, NULL, legrand_times10, NULL }, + { 0, NULL, NULL, NULL } }; static const char *legrand_times100k(double value) @@ -86,7 +90,8 @@ static const char *legrand_times100k(double value) } static info_lkp_t legrand_times100k_info[] = { - { 0, NULL, legrand_times100k } + { 0, NULL, legrand_times100k, NULL }, + { 0, NULL, NULL, NULL } }; static const char *legrand_times1M(double value) @@ -97,7 +102,8 @@ static const char *legrand_times1M(double value) } static info_lkp_t legrand_times1M_info[] = { - { 0, NULL, legrand_times1M } + { 0, NULL, legrand_times1M, NULL }, + { 0, NULL, NULL, NULL } }; static const char *legrand_times10M(double value) @@ -108,7 +114,8 @@ static const char *legrand_times10M(double value) } static info_lkp_t legrand_times10M_info[] = { - { 0, NULL, legrand_times10M } + { 0, NULL, legrand_times10M, NULL }, + { 0, NULL, NULL, NULL } }; /* --------------------------------------------------------------- */ From 7529ee7342d8c116f9a67fa8f5eff8ac932c7894 Mon Sep 17 00:00:00 2001 From: Stephan Blecher <_github@blecher.at> Date: Wed, 20 Oct 2021 16:33:58 +0200 Subject: [PATCH 16/16] drivers/hidparser.c --- drivers/hidparser.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/hidparser.c b/drivers/hidparser.c index 74a01fa939..c5879e9a69 100644 --- a/drivers/hidparser.c +++ b/drivers/hidparser.c @@ -452,7 +452,7 @@ void GetValue(const unsigned char *Buf, HIDData_t *pData, long *pValue) int Weight, Bit; unsigned long mask, signbit, magMax, magMin; - long value = 0; + long value = 0, range; Bit = pData->Offset + 8; /* First byte of report is report ID */ @@ -464,6 +464,13 @@ void GetValue(const unsigned char *Buf, HIDData_t *pData, long *pValue) } } + range = pData->LogMax - pData->LogMin + 1; + if (range <= 0) { + /* makes no sense, give up */ + *pValue = value; + return; + } + /* translate Value into a signed/unsigned value in the range LogMin..LogMax, as appropriate. See HID spec, p.38: "If both the Logical Minimum and Logical Maximum extents are defined as