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 @@ -83,6 +83,11 @@ https://github.com/networkupstools/nut/milestone/13
reconnection behavior. [issue #3510, PR #3543]

- `nutdrv_qx` driver updates:
* Added the `richcomm-svc` USB communication subdriver for older
session-initialized Richcomm bridges. It configures the bridge before
reusing the existing Armac command framing and Megatec protocol. Polling
and a quick battery test were verified with an SVC V-1000F
(`0925:1234`, `UPS USB MON V1.4`). [issue #3562]
* Only claim a USB device as "supported" during discovery out of the box
if `subdriver_command` was assigned (`1A86:7523` is used by CH340/341
USB chips not only in UPSes). [issue #3410]
Expand Down
1 change: 1 addition & 0 deletions data/driver.list.in
Original file line number Diff line number Diff line change
Expand Up @@ -1453,6 +1453,7 @@
"SuperPower" "ups" "2" "HP360, Hope-550" "" "blazer_ser"

"SVC" "ups" "2" "VP-1250-LCD 1250VA" "(USB ID 0925:1234, armac subdriver, megatec protocol)" "nutdrv_qx" # https://github.com/networkupstools/nut/pull/3485
"SVC" "ups" "1" "V-1000F" "USB (USB ID 0925:1234, product UPS USB MON V1.4)" "nutdrv_qx port=auto vendorid=0925 productid=1234 protocol=megatec subdriver=richcomm-svc" # https://github.com/networkupstools/nut/issues/3562 ; polling and quick battery test verified on physical hardware; deep battery tests and load or shutdown commands untested

"SVEN" "ups" "2" "Power Pro+ series" "USB" "blazer_usb (USB ID ffff:0000)"
"SVEN" "ups" "2" "Power Pro+ series" "USB" "blazer_usb (USB ID 05b8:0000)"
Expand Down
27 changes: 26 additions & 1 deletion docs/man/nutdrv_qx.txt
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ include::nut_usb_addvars.txt[]

*subdriver =* 'string'::
Select a USB communication subdriver to use.
You have a choice between *ablerex*, *armac*, *cypress*, *fabula*, *fuji*, *gtec*, *hunnox*, *ippon*, *krauler*, *omron*, *phoenix*, *phoenixtec*, *sgs* and *snr*.
You have a choice between *ablerex*, *armac*, *cypress*, *fabula*, *fuji*, *gtec*, *hunnox*, *ippon*, *krauler*, *omron*, *phoenix*, *phoenixtec*, *richcomm-svc*, *sgs* and *snr*.
+
Run the driver program with the `--help` option to see the exact list of
`subdriver` values it would currently recognize.
Expand All @@ -514,6 +514,31 @@ specific: its banner is "2004 Richcomm Technologies, Inc. Dec 27 2005 ver 1.1."
Maybe other Richcomm UPSes would work with this -- maybe better than with
the older standalone `richcomm_usb` driver.

*'richcomm-svc' subdriver*::
This USB communication subdriver supports older session-initialized Richcomm
USB-to-UART bridges. It was developed and tested with an SVC V-1000F whose
USB ID is `0925:1234` and product string is `UPS USB MON V1.4`.
+
The bridge must be configured for 2400 baud and receive a session-opening
sequence before it accepts Megatec commands. After that initialization this
subdriver reuses the Armac command framing and the existing Megatec protocol,
with the 6-byte interrupt reads required by this hardware.
Because `0925:1234` is shared by devices with different communication details,
this mode is not selected automatically. Configure it explicitly, for example:
+
----
[svc-v1000f]
driver = "nutdrv_qx"
port = "auto"
subdriver = "richcomm-svc"
protocol = "megatec"
vendorid = "0925"
productid = "1234"
----
+
Polling and `test.battery.start.quick` were verified on physical hardware.
Deep battery tests and load or shutdown commands were not tested.

*'fabula' subdriver*::
This subdriver, meant to be used with the 'megatec' protocol, does *not* support the various *test.battery* commands.
Plus, the *shutdown.return* command ignores the values set in 'ups.delay.start'/*ondelay* and makes the UPS turn on the load as soon as power is back.
Expand Down
111 changes: 107 additions & 4 deletions drivers/nutdrv_qx.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
# define DRIVER_NAME "Generic Q* Serial driver"
#endif /* QX_USB */

#define DRIVER_VERSION "0.54"
#define DRIVER_VERSION "0.55"

#ifdef QX_SERIAL
# include "serial.h"
Expand Down Expand Up @@ -2239,6 +2239,9 @@ static struct {
uint16_t out_wMaxPacketSize;
} armac_endpoint_cache = { .initialized = FALSE, .ok = FALSE };

static bool_t richcomm_svc_baud_initialized = FALSE;
static bool_t richcomm_svc_session_initialized = FALSE;

static void load_armac_endpoint_cache(void)
{
#if WITH_LIBUSB_1_0
Expand Down Expand Up @@ -2339,16 +2342,82 @@ static void load_armac_endpoint_cache(void)
*/
#define ARMAC_READ_SIZE_FOR_CONTROL 8
#define ARMAC_READ_SIZE_FOR_INTERRUPT 64
static int armac_command(const char *cmd, size_t cmdlen, char *buf, size_t buflen)
/* V-1000F returns LIBUSB_ERROR_OVERFLOW when asked for 8 bytes. */
#define RICHCOMM_SVC_READ_SIZE_FOR_CONTROL 6

static int richcomm_svc_initialize(void)
{
char report[64];
int ret;
size_t offset;

if (!richcomm_svc_baud_initialized) {
report[0] = (char)0x80;
report[1] = (char)0xd0;
report[2] = 0x00;
report[3] = 0x00;

ret = usb_control_msg(udev,
USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE,
0x09, 0x0200, 0,
(usb_ctrl_charbuf)report, 4, 5000);
if (ret <= 0) {
upsdebugx(1, "richcomm-svc: failed to configure 2400-baud transport: %s (%d)",
ret ? nut_usb_strerror(ret) : "timeout", ret);
return ret;
}
if (ret != 4) {
upsdebugx(1, "richcomm-svc: short baud-rate report transfer (%d of 4 bytes)", ret);
return -1;
}

richcomm_svc_baud_initialized = TRUE;
usleep(100000);
}

if (richcomm_svc_session_initialized) {
return 1;
}

/* SVC V-1000F (0925:1234) requires this 64-byte session-opening
* sequence before its USB-to-UART bridge accepts Megatec commands. */
report[0] = 0x40;
for (offset = 1; offset < sizeof(report); offset++) {
report[offset] = (char)(offset * 73u + 29u);
}

for (offset = 0; offset < sizeof(report); offset += 4) {
ret = usb_control_msg(udev,
USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE,
0x09, 0x0200, 0,
(usb_ctrl_charbuf)(report + offset), 4, 5000);
if (ret <= 0) {
upsdebugx(1, "richcomm-svc: failed to send session-opening report: %s (%d)",
ret ? nut_usb_strerror(ret) : "timeout", ret);
return ret;
}
if (ret != 4) {
upsdebugx(1, "richcomm-svc: short session-opening report transfer (%d of 4 bytes)", ret);
return -1;
}
}

richcomm_svc_session_initialized = TRUE;
upsdebugx(3, "richcomm-svc: session-opening sequence sent");
usleep(100000);
return 1;
}

static int armac_command_internal(const char *cmd, size_t cmdlen, char *buf, size_t buflen, bool_t use_richcomm_svc)
{
char tmpbuf[ARMAC_READ_SIZE_FOR_INTERRUPT];
int ret = 0;
size_t i, bufpos;
size_t i, bufpos, idle_reports = 0;
const size_t cmdstrlen = strnlen(cmd, cmdlen); /* Length of cmd string (excluding terminating '\0'), or cmdlen if the string is too long */
const size_t cmddatalen = cmdstrlen >= cmdlen ? cmdlen : cmdstrlen + 1; /* Amount of useful/valid data bytes in cmd string (max=cmdlen, or length of cmd+'\0' if the string is short enough) */
const size_t tmplen = cmddatalen > sizeof(tmpbuf) ? sizeof(tmpbuf) : cmddatalen; /* How much of cmd[] we can copy into tmp[] so it fits (and remains useful), including the terminating '\0' */
bool_t use_interrupt = FALSE;
int read_size = ARMAC_READ_SIZE_FOR_CONTROL;
int read_size = use_richcomm_svc ? RICHCOMM_SVC_READ_SIZE_FOR_CONTROL : ARMAC_READ_SIZE_FOR_CONTROL;

/* UPS ignores (doesn't echo back) unsupported commands which makes
* the initialization long. List commands tested to be unsupported:
Expand Down Expand Up @@ -2376,6 +2445,13 @@ static int armac_command(const char *cmd, size_t cmdlen, char *buf, size_t bufle
load_armac_endpoint_cache();
}

if (use_richcomm_svc) {
ret = richcomm_svc_initialize();
if (ret <= 0) {
return ret;
}
}

for (i = 0; unsupported[i] != NULL; i++) {
if (strcmp(cmd, unsupported[i]) == 0) {
upsdebugx(2,
Expand Down Expand Up @@ -2492,6 +2568,15 @@ static int armac_command(const char *cmd, size_t cmdlen, char *buf, size_t bufle
*/
bytes_available = (unsigned char)tmpbuf[0] & 0x3f;
if (bytes_available == 0) {
/* This bridge can emit idle HID reports before UART data. */
if (use_richcomm_svc && bufpos == 0) {
if (++idle_reports > 5) {
upsdebugx(4, "richcomm-svc: no UART reply after idle HID reports");
break;
}
upsdebugx(4, "richcomm-svc: ignoring idle HID report before response");
continue;
}
/* End of transfer */
break;
}
Expand Down Expand Up @@ -2556,6 +2641,18 @@ static int armac_command(const char *cmd, size_t cmdlen, char *buf, size_t bufle
return (int)bufpos;
}

static int armac_command(const char *cmd, size_t cmdlen, char *buf, size_t buflen)
{
return armac_command_internal(cmd, cmdlen, buf, buflen, FALSE);
}

static int richcomm_svc_command(const char *cmd, size_t cmdlen, char *buf, size_t buflen)
{
/* The bridge needs one-time transport initialization, then uses the
* existing Armac command framing with the Megatec protocol. */
return armac_command_internal(cmd, cmdlen, buf, buflen, TRUE);
}


static void *cypress_subdriver(USBDevice_t *device)
{
Expand Down Expand Up @@ -3387,6 +3484,7 @@ void upsdrv_shutdown(void)
{ "snr", &snr_command },
{ "ablerex", &ablerex_command },
{ "armac", &armac_command },
{ "richcomm-svc", &richcomm_svc_command },
{ "gtec", &gtec_command },
{ NULL, NULL }
};
Expand Down Expand Up @@ -4076,6 +4174,11 @@ static ssize_t qx_command(const char *cmd, size_t cmdlen, char *buf, size_t bufl
return ret;
}

if (subdriver_command == &richcomm_svc_command) {
richcomm_svc_baud_initialized = FALSE;
richcomm_svc_session_initialized = FALSE;
}

reconnect_trying(RECONNECT_UPDATEINFO);
}

Expand Down
Loading