Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions NEWS.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,11 @@ https://github.com/networkupstools/nut/milestone/13
`ups.power.nominal`, `ups.realpower.nominal` and the computed
`ups.load`. [issue #3500]

- `powerpanel` driver updates:
* Added an opt-in, query-only USB text transport for OLS2000ERT2UA
and decode reported output current, watts and VA. Serial controls
remain unchanged; USB electrical-output commands are not implemented.

- `richcomm_usb` driver updates:
* Completed support for NUT standard USB configuration options, and fixed
some problems possible when iterating devices. [issue #1768, PR #3477]
Expand Down
1 change: 1 addition & 0 deletions data/driver.list.in
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@

"Cyber Power Systems" "ups" "1" "550SL" "" "genericups upstype=7"
"Cyber Power Systems" "ups" "1" "725SL" "" "genericups upstype=7"
"Cyber Power Systems" "ups" "1" "OLS2000ERT2UA" "USB, query-only" "powerpanel transport=usb"
"Cyber Power Systems" "ups" "1" "CPS1100AVR" "" "powerpanel"
"Cyber Power Systems" "ups" "1" "CPS1200AVR" "" "powerpanel"
"Cyber Power Systems" "ups" "1" "CPS1250AVR" "" "powerpanel"
Expand Down
43 changes: 40 additions & 3 deletions docs/man/powerpanel.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ POWERPANEL(8)
NAME
----

powerpanel - Driver for serial PowerPanel Plus compatible UPS equipment
powerpanel - Driver for PowerPanel Plus compatible UPS equipment

SYNOPSIS
--------
Expand All @@ -28,11 +28,38 @@ If your Cyber Power Systems UPS has a USB port, you may wish to use the
linkman:usbhid-ups[8] driver. The linkman:snmp-ups[8] driver supports several
network cards via SNMP.

The OLS2000ERT2UA also supports an opt-in USB text transport, requiring a
libusb-1.0 build. This mode provides read-only monitoring, including actual
output current, watts and VA reported by the device. It does not provide UPS
power-off commands or writable settings. Serial operation remains the default.
If no explicit transport is configured, `port = auto` selects USB mode.
Static battery topology and hardware shutdown delays are not reported in USB
mode because their protocol interpretation has not been validated.

Only one USB device with ID `0764:0601` may be connected for this initial
implementation, and its model must identify as OLS2000ERT2UA. Other models have
not been validated. Do not run another USB driver against the same UPS.

Example linkman:ups.conf[5] section:

----
[myups]
driver = powerpanel
port = auto
transport = usb
protocol = text
----

EXTRA ARGUMENTS
---------------

This driver supports the following optional settings in linkman:ups.conf[5]:

*transport=*['serial,usb']::
Select the connection transport. The default is serial, except that
`port = auto` selects USB. USB currently supports
only the text protocol and read-only monitoring of OLS2000ERT2UA.

*protocol=*['text,binary']::
Override the default autodetection of the protocol.

Expand All @@ -48,12 +75,12 @@ Like manufacturer above.

*ondelay=*'value'::
Time to wait before switching on the UPS (1 - 9999 minutes, 0=indefinite).
Only available with the text protocol driver (see <<_support_status,Support Status>>).
Only available with the serial text protocol driver (see <<_support_status,Support Status>>).

*offdelay=*'value'::
Time to wait before shutting down the UPS (6 - 600 seconds). Values below 60
seconds will be truncated to 6 seconds intervals, values above 60 seconds to
60 seconds intervals. Only available with the text protocol driver (see
60 seconds intervals. Only available with the serial text protocol driver (see
<<_support_status,Support Status>>).

VARIABLES
Expand All @@ -63,6 +90,11 @@ Depending on the type of your UPS unit, some of the following variables may
be changed with linkman:upsrw[8]. If the driver can't read a variable from the
UPS, it will not be made available.

USB text mode does not mark any settings writable. Where the status reply
contains them, `output.current` is reported in amperes, `ups.realpower` in
watts, and `ups.power` in VA. These values are read from the UPS rather than
estimated from its load percentage.

*input.transfer.high*::
writable: high transfer voltage point in V

Expand All @@ -84,6 +116,11 @@ COMMANDS
Depending on the type of your UPS unit, some of the following commands may
be available.

The commands in this section apply to serial connections. USB text mode does
not register instant commands or issue electrical-output controls. In
particular, a USB-mode driver shutdown request fails rather than guessing a
power-off command.

* test.battery.start.quick, test.battery.stop

* beeper.enable, beeper.disable, beeper.toggle
Expand Down
4 changes: 3 additions & 1 deletion docs/nut.dict
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
personal_ws-1.1 en 3917 utf-8
personal_ws-1.1 en 3921 utf-8
AAC
AAS
ABI
Expand Down Expand Up @@ -365,6 +365,7 @@ EPFCLCD
EPIPE
EPO
EPS
ERT
ESC
ESS
ESV
Expand Down Expand Up @@ -920,6 +921,7 @@ OFFDURATION
OID
OIDs
OLHVT
OLS
OMNIVS
OMNIVSINT
OMRON
Expand Down
6 changes: 5 additions & 1 deletion drivers/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,12 @@ oneac_SOURCES = oneac.c
optiups_SOURCES = optiups.c
powercom_SOURCES = powercom.c
powercom_LDADD = $(LDADD_DRIVERS_SERIAL) -lm
powerpanel_SOURCES = powerpanel.c powerp-bin.c powerp-txt.c
powerpanel_SOURCES = powerpanel.c powerp-bin.c powerp-txt.c powerp-txt-parser.c powerp-txt-parser.h
powerpanel_LDADD = $(LDADD_DRIVERS_SERIAL) -lm
if WITH_LIBUSB_1_0
powerpanel_SOURCES += powerp-usb.c powerp-usb.h
powerpanel_LDADD += $(LIBUSB_LIBS)
endif WITH_LIBUSB_1_0
powervar_cx_ser_SOURCES = powervar_cx_ser.c powervar_cx.c
powervar_cx_ser_CFLAGS = $(AM_CFLAGS) -DPVAR_SERIAL=1
rhino_SOURCES = rhino.c
Expand Down
183 changes: 183 additions & 0 deletions drivers/powerp-txt-parser.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/*
* powerp-txt.c - Model specific routines for CyberPower text
* protocol UPSes
*
* Copyright (C)
* 2007 Doug Reynolds <mav@wastegate.net>
* 2007-2008 Arjen de Korte <adkorte-guest@alioth.debian.org>
* 2012-2016 Timothy Pearson <kb9vqf@pearsoncomputing.net>
*
* 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 "common.h"
#include "powerp-txt-parser.h"
#include <ctype.h>
#include <float.h>
#include <limits.h>

static int parse_current(const char *text, float *out)
{
char *end;
double value;
errno = 0;
value = strtod(text, &end);
if (end == text || *end || errno == ERANGE || !(value >= 0 && value <= FLT_MAX)) {
return -1;
}
*out = (float)value;
return 0;
}

static int parse_power(const char *text, int *out)
{
char *end;
long value;
errno = 0;
value = strtol(text, &end, 10);
if (end == text || *end || errno == ERANGE || value < 0 || value > INT_MAX) {
return -1;
}
*out = (int)value;
return 0;
}

/*
* Status format from the D query (wire examples):
* READ #I119.0O119.0L000B100T027F060.0S..\r
* #I118.0O118.0L029B100F060.0R0218S..\r
* #I118.1O118.1L13B100V27.5F60.0HF60.0R65Q1.4S\x80\x84\xc0\x88\x80W\r (CST135XLU)
* 01234567890123456789012345678901234567890123
* 0 1 2 3 4
*/

int powpan_parse_status(const char *answer, size_t length, powpan_status_t *status)
{
size_t i, ofs = 0;
size_t valid = 0;
int code = -1;
char value[32];
char seen_i_freq = 0;
int seen_flags = 0;
if (!answer || !status || length < 2) {
return -1;
}
memset(status, 0, sizeof(*status));
if (answer[0] != '#') {
return -1;
}

for (i = 1; i <= length; i++) {
if (i == length || isalpha((unsigned char)(answer[i]))) {
if (i < length && (code == 'Q' || code == 'J' || code == 'E')
&& islower((unsigned char)answer[i])) {
return -1;
}
value[ofs] = '\0';
valid++;

switch (code) {
case 'I':
status->i_volt = strtof(value, NULL);
break;
case 'O':
status->o_volt = strtof(value, NULL);
break;
case 'L':
status->o_load = strtol(value, NULL, 10);
break;
case 'B':
status->b_chrg = strtol(value, NULL, 10);
break;
case 'V':
status->b_volt = strtof(value, NULL);
status->has_b_volt = 1;
break;
case 'T':
status->u_temp = strtol(value, NULL, 10);
status->has_u_temp = 1;
break;
case 'F':
status->i_freq = strtof(value, NULL);
seen_i_freq = 1;
break;
case 'H':
status->o_freq = strtof(value, NULL);
status->has_o_freq = 1;
break;
case 'R':
status->runtime = strtol(value, NULL, 10);
status->has_runtime = 1;
break;
case 'Q':
if (status->has_o_current || parse_current(value, &status->o_current) < 0) {
return -1;
}
status->has_o_current = 1;
break;
case 'J':
if (status->has_realpower || parse_power(value, &status->realpower) < 0) {
return -1;
}
status->has_realpower = 1;
break;
case 'E':
if (status->has_power || parse_power(value, &status->power) < 0) {
return -1;
}
status->has_power = 1;
break;
case 'S':
if (ofs < 2) {
return -1;
}
memcpy(&status->flags, value, 2);
seen_flags = 1;
break;
default:
/* We didn't really find valid data */
valid--;
break;
}

if (i == length) {
break;
}
code = answer[i];
ofs = 0;

/*
* Depending on device/firmware, the output frequency is coded as
* either an H, HF, or a second F (seen once transfered to battery)
*/
if (seen_i_freq && code == 'F')
code = 'H';

continue;
}

if (ofs >= sizeof(value) - 1) {
return -1;
}
value[ofs++] = answer[i];
}

/* if we didn't get at least 3 values consider it a failure */
if (valid < 3 || !seen_flags) {
return -1;
}

return 0;
}
53 changes: 53 additions & 0 deletions drivers/powerp-txt-parser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* powerp-txt.c - Model specific routines for CyberPower text
* protocol UPSes
*
* Copyright (C)
* 2007 Doug Reynolds <mav@wastegate.net>
* 2007-2008 Arjen de Korte <adkorte-guest@alioth.debian.org>
* 2012-2016 Timothy Pearson <kb9vqf@pearsoncomputing.net>
*
* 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 POWERP_TXT_PARSER_H
#define POWERP_TXT_PARSER_H
#include <stddef.h>

typedef struct {
float i_volt;
float o_volt;
int o_load;
int b_chrg;
unsigned char has_u_temp;
int u_temp;
float i_freq;
unsigned char flags[2];
unsigned char has_b_volt;
float b_volt;
unsigned char has_o_freq;
float o_freq;
unsigned char has_runtime;
int runtime;
unsigned char has_o_current;
float o_current;
unsigned char has_realpower;
int realpower;
unsigned char has_power;
int power;
} powpan_status_t;

int powpan_parse_status(const char *answer, size_t length, powpan_status_t *status);
#endif
Loading