Hello, all. Some time ago I bought myself a UPS which was advertised as USB-HID. It was a surprise to learn that while it definitely is recognized as USB device, the HID descriptor has no UPS pages at all. The only software it came with was a windows program written in visual basic. Trying to research this topic I found a reference to energizer USB ups driver and learned about variety of UPSes that speak megatec protocol and have proprietary usb-to-serial connectors. The energizer driver did not work for me - my converter was different. The usb-serial chip used in my UPS (SVEN Pro+ usb series) is a complete OEM hack. Apparently the only reason it is a HID device is so usermode application can access it without driver in windows. Its HID tables are flawed, it is impossible to learn a valid report size from it. After some reverse-engineering of windows driver I learned that all these constants were hardcoded in driver directly. In order to write a driver for it I had two options -- either add support to energizer driver or add another communication layer to megatec protocol. I did not like energizer driver approach - the energizer driver is a hack by itself, does not support all of megatec protocol (and especially assumes that UPS is 110V rated rather then use F command), and uses hiddev. In my case there was no way to use a hiddev (tables ar flawed) and anyways libusb seemed to be much more robust alternative. I choose to change megatec driver. Since usb drivers go to separate package I creates a new driver megatec_usb . The changes in megatec.c were minimal - only replace all serial send/getline with calls to a communication layer and change two lines in init/cleanup. Now megatec.c links with megatec_ser.c that impements serial communication layer to produce old megatec driver, and megatec.c+megatec_usb.c+libusb.c produce megatec_usb driver. (this is a good point to actually look at the attached patch) The architecture of megatec_usb itself is modular. For each device it knows about there are two routines (send/recv) that do actual transfer. This way in order to support yet another usb-serial controller one only needs to add corresponding functions and an entry into device table. For now the driver supports only single chip by agiler that happen to be in my UPS. It might be that this driver will support energizer UPSes too, but naturally I have no chance to test that. Anyway changing it to support all hardware that is supported by energizer driver should be trivial - this way energizer driver (the only remaining "old" hid driver) may be replaced by megatec_usb. While changes to megatec.c are marginal and megatec_ser.c is a straightforward one, i did not test it at all. megatec_usb works fine on my system (amd64 debian etch) for several days. So someone with an serial megatec UPS have to look at it, or at least code review it. Also it probably will make sense to make a call for users of such UPSes and try to see if that hardware will work with this driver. I just dont want the results of my work to disappear into void. Is it something that can go into main tree? Please let me know what you think. I can also do all the grunt work (write man page, add to a list of supported devices, change debian USB hotplug script, etc) if this driver will be included. Thanks. -------------- next part -------------- diff -Naur drivers_orig/Makefile.am drivers/Makefile.am --- drivers_orig/Makefile.am 2006-12-04 13:08:58.000000000 +0300 +++ drivers/Makefile.am 2006-12-04 13:05:14.000000000 +0300 @@ -22,7 +22,7 @@ nitram oneac optiups powercom rhino safenet skel sms solis tripplite \ tripplitesu upscode2 victronups SNMP_DRIVERLIST = snmp-ups -USB_LIBUSB_DRIVERLIST = newhidups bcmxcp_usb tripplite_usb +USB_LIBUSB_DRIVERLIST = newhidups bcmxcp_usb tripplite_usb megatec_usb USB_HIDDEV_DRIVERLIST = hidups energizerups USB_DRIVERLIST = $(USB_LIBUSB_DRIVERLIST) $(USB_HIDDEV_DRIVERLIST) @@ -83,7 +83,7 @@ isbmex_LDADD = $(LDADD) -lm liebert_SOURCES = liebert.c masterguard_SOURCES = masterguard.c -megatec_SOURCES = megatec.c +megatec_SOURCES = megatec.c megatec_ser.c metasys_SOURCES = metasys.c mge_shut_SOURCES = mge-shut.c hidparser.c mge_utalk_SOURCES = mge-utalk.c @@ -133,6 +133,10 @@ bcmxcp_usb_CFLAGS = $(AM_CFLAGS) $(LIBUSB_CFLAGS) bcmxcp_usb_LDADD = $(LDADD_DRIVERS) $(LIBUSB_LIBS) +megatec_usb_SOURCES = megatec.c megatec_usb.c libusb.c +megatec_usb_CFLAGS = $(AM_CFLAGS) $(LIBUSB_CFLAGS) +megatec_usb_LDADD = $(LDADD_DRIVERS) $(LIBUSB_LIBS) + # USB-over-serial newmge_shut_SOURCES = newhidups.c libshut.c libhid.c hidparser.c mge-hid.c newmge_shut_CFLAGS = $(AM_CFLAGS) -DSHUT_MODE diff -Naur drivers_orig/megatec.c drivers/megatec.c --- drivers_orig/megatec.c 2006-12-04 13:08:58.000000000 +0300 +++ drivers/megatec.c 2006-12-03 16:02:35.000000000 +0300 @@ -23,7 +23,6 @@ #include "main.h" -#include "serial.h" #include "megatec.h" #include <stdio.h> @@ -44,9 +43,6 @@ #define IDENT_MAXTRIES 5 #define IDENT_MINSUCCESS 3 -#define SEND_PACE 100000 /* 100ms interval between chars */ -#define READ_TIMEOUT 2 /* 2 seconds timeout on read */ - #define MAX_START_DELAY 9999 #define MAX_SHUTDOWN_DELAY 99 @@ -167,7 +163,6 @@ /* I know, macros should evaluate their arguments only once */ #define CLAMP(x, min, max) (((x) < (min)) ? (min) : (((x) > (max)) ? (max) : (x))) - static float batt_charge_pct(float battvolt) { float value; @@ -178,15 +173,14 @@ return value * 100; } - static int check_ups(void) { char buffer[RECV_BUFFER_LEN]; int ret; upsdebugx(2, "Sending \"F\" command..."); - ser_send_pace(upsfd, SEND_PACE, "F%c", ENDCHAR); - ret = ser_get_line(upsfd, buffer, RECV_BUFFER_LEN, ENDCHAR, IGNCHARS, READ_TIMEOUT, 0); + comm->send("F%c", ENDCHAR); + ret = comm->recv(buffer, RECV_BUFFER_LEN, ENDCHAR, IGNCHARS); if (ret < F_CMD_REPLY_LEN) { upsdebugx(2, "Wrong answer to \"F\" command."); @@ -195,8 +189,8 @@ upsdebugx(2, "\"F\" command successful."); upsdebugx(2, "Sending \"Q1\" command..."); - ser_send_pace(upsfd, SEND_PACE, "Q1%c", ENDCHAR); - ret = ser_get_line(upsfd, buffer, RECV_BUFFER_LEN, ENDCHAR, IGNCHARS, READ_TIMEOUT, 0); + comm->send("Q1%c", ENDCHAR); + ret = comm->recv(buffer, RECV_BUFFER_LEN, ENDCHAR, IGNCHARS); if (ret < Q1_CMD_REPLY_LEN) { upsdebugx(2, "Wrong answer to \"Q1\" command."); @@ -243,8 +237,8 @@ int ret; upsdebugx(1, "Asking for UPS information (\"I\" command)..."); - ser_send_pace(upsfd, SEND_PACE, "I%c", ENDCHAR); - ret = ser_get_line(upsfd, buffer, RECV_BUFFER_LEN, ENDCHAR, IGNCHARS, READ_TIMEOUT, 0); + comm->send("I%c", ENDCHAR); + ret = comm->recv(buffer, RECV_BUFFER_LEN, ENDCHAR, IGNCHARS); if (ret < I_CMD_REPLY_LEN) { upsdebugx(1, "UPS doesn't return any information about itself."); @@ -273,8 +267,8 @@ int ret; upsdebugx(1, "Asking for UPS power ratings (\"F\" command)..."); - ser_send_pace(upsfd, SEND_PACE, "F%c", ENDCHAR); - ret = ser_get_line(upsfd, buffer, RECV_BUFFER_LEN, ENDCHAR, IGNCHARS, READ_TIMEOUT, 0); + comm->send("F%c", ENDCHAR); + ret = comm->recv(buffer, RECV_BUFFER_LEN, ENDCHAR, IGNCHARS); if (ret < F_CMD_REPLY_LEN) { upsdebugx(1, "UPS doesn't return any information about its power ratings."); @@ -296,8 +290,8 @@ int ret; upsdebugx(1, "Asking for UPS status (\"Q1\" command)..."); - ser_send_pace(upsfd, SEND_PACE, "Q1%c", ENDCHAR); - ret = ser_get_line(upsfd, buffer, RECV_BUFFER_LEN, ENDCHAR, IGNCHARS, READ_TIMEOUT, 0); + comm->send("Q1%c", ENDCHAR); + ret = comm->recv(buffer, RECV_BUFFER_LEN, ENDCHAR, IGNCHARS); if (ret < Q1_CMD_REPLY_LEN) { upsdebugx(1, "UPS doesn't return any information about its status."); @@ -468,7 +462,7 @@ upsh.setvar = setvar; /* clean up a possible shutdown in progress */ - ser_send_pace(upsfd, SEND_PACE, "C%c", ENDCHAR); + comm->send("C%c", ENDCHAR); upsdebugx(1, "Done setting up the UPS."); } @@ -561,16 +555,16 @@ { upslogx(LOG_INFO, "Shutting down UPS immediately."); - ser_send_pace(upsfd, SEND_PACE, "C%c", ENDCHAR); - ser_send_pace(upsfd, SEND_PACE, "S00R%04d%c", start_delay, ENDCHAR); + comm->send("C%c", ENDCHAR); + comm->send("S00R%04d%c", start_delay, ENDCHAR); } int instcmd(const char *cmdname, const char *extra) { if (strcasecmp(cmdname, "test.battery.start") == 0) { - ser_send_pace(upsfd, SEND_PACE, "C%c", ENDCHAR); - ser_send_pace(upsfd, SEND_PACE, "T%c", ENDCHAR); + comm->send("C%c", ENDCHAR); + comm->send("T%c", ENDCHAR); upslogx(LOG_INFO, "Start battery test for 10 seconds."); @@ -578,8 +572,8 @@ } if (strcasecmp(cmdname, "shutdown.return") == 0) { - ser_send_pace(upsfd, SEND_PACE, "C%c", ENDCHAR); - ser_send_pace(upsfd, SEND_PACE, "S%02dR%04d%c", shutdown_delay, start_delay, ENDCHAR); + comm->send("C%c", ENDCHAR); + comm->send("S%02dR%04d%c", shutdown_delay, start_delay, ENDCHAR); upslogx(LOG_INFO, "Shutdown (return) initiated."); @@ -587,8 +581,8 @@ } if (strcasecmp(cmdname, "shutdown.stayoff") == 0) { - ser_send_pace(upsfd, SEND_PACE, "C%c", ENDCHAR); - ser_send_pace(upsfd, SEND_PACE, "S%02dR0000%c", shutdown_delay, ENDCHAR); + comm->send("C%c", ENDCHAR); + comm->send("S%02dR0000%c", shutdown_delay, ENDCHAR); upslogx(LOG_INFO, "Shutdown (stayoff) initiated."); @@ -596,7 +590,7 @@ } if (strcasecmp(cmdname, "shutdown.stop") == 0) { - ser_send_pace(upsfd, SEND_PACE, "C%c", ENDCHAR); + comm->send("C%c", ENDCHAR); upslogx(LOG_INFO, "Shutdown canceled."); @@ -604,7 +598,7 @@ } if (strcasecmp(cmdname, "load.on") == 0) { - ser_send_pace(upsfd, SEND_PACE, "C%c", ENDCHAR); + comm->send("C%c", ENDCHAR); upslogx(LOG_INFO, "Turning load on."); @@ -612,8 +606,8 @@ } if (strcasecmp(cmdname, "load.off") == 0) { - ser_send_pace(upsfd, SEND_PACE, "C%c", ENDCHAR); - ser_send_pace(upsfd, SEND_PACE, "S00R0000%c", ENDCHAR); + comm->send("C%c", ENDCHAR); + comm->send("S00R0000%c", ENDCHAR); upslogx(LOG_INFO, "Turning load off."); @@ -686,21 +680,19 @@ void upsdrv_banner(void) { - printf("Network UPS Tools - Megatec protocol driver %s (%s)\n", DRV_VERSION, UPS_VERSION); + printf("Network UPS Tools - Megatec protocol driver %s[%s] (%s)\n", DRV_VERSION, comm->name, UPS_VERSION); printf("Carlos Rodrigues (c) 2003-2006\n\n"); } - void upsdrv_initups(void) { - upsfd = ser_open(device_path); - ser_set_speed(upsfd, device_path, B2400); + comm->open(device_path); } void upsdrv_cleanup(void) { - ser_close(upsfd, device_path); + comm->close(device_path); } diff -Naur drivers_orig/megatec.h drivers/megatec.h --- drivers_orig/megatec.h 2006-12-04 13:08:58.000000000 +0300 +++ drivers/megatec.h 2006-12-03 16:01:19.000000000 +0300 @@ -22,3 +22,16 @@ */ #define DRV_VERSION "1.4" + +/* comm driver */ +typedef struct { + const char *name; + int (*open)(const char*param); + void (*close)(); + int (*send)(const char *fmt,...); + int (*recv)(char *buffer,size_t buffer_len,char endchar,const char *ignchars); +} megatec_comm_t; + +extern megatec_comm_t* comm; + + diff -Naur drivers_orig/megatec_ser.c drivers/megatec_ser.c --- drivers_orig/megatec_ser.c 1970-01-01 03:00:00.000000000 +0300 +++ drivers/megatec_ser.c 2006-12-04 12:44:46.000000000 +0300 @@ -0,0 +1,82 @@ +/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: t; -*- + * + * megatec_ser.c: serial communication layer for Megatec protocol based UPSes + * + * Copyright (C) Andrey Lelikov <nut-driver@lelik.org> + * + * megatec_ser.c created on 3-Oct-2006 + * + * 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 "megatec.h" +#include "serial.h" + +#include <stdio.h> +#include <limits.h> +#include <string.h> + +#define SEND_PACE 100000 /* 100ms interval between chars */ +#define READ_TIMEOUT 2 /* 2 seconds timeout on read */ + +static int comm_ser_open(const char *param) +{ + upsfd = ser_open(param); + ser_set_speed(upsfd, device_path, B2400); + return (upsfd<0) ? -1 : 0; +} + +static void comm_ser_close(const char *param) +{ + ser_close(upsfd, param); +} + +static int comm_ser_send(const char *fmt,...) +{ + char buf[128]; + size_t len; + va_list ap; + + va_start(ap, fmt); + + len = vsnprintf(buf, sizeof(buf), fmt, ap); + + va_end(ap); + + if ((len < 1) || (len >= (int) sizeof(buf))) + upslogx(LOG_WARNING, "comm_ser_send: vsnprintf needed more " + "than %d bytes", (int)sizeof(buf)); + + return ser_send_buf_pace(upsfd, SEND_PACE, (unsigned char*)buf, len); +} + +static int comm_ser_recv(char *buffer,size_t buffer_len,char endchar,const char *ignchars) +{ + return ser_get_line(upsfd, buffer, buffer_len, endchar, ignchars, READ_TIMEOUT, 0); +} + +static megatec_comm_t comm_ser = +{ + .name = "serial", + .open = &comm_ser_open, + .close = &comm_ser_close, + .send = &comm_ser_send, + .recv = &comm_ser_recv +}; + +megatec_comm_t *comm = & comm_ser; + +/* EOF - megatec_ser.c */ diff -Naur drivers_orig/megatec_usb.c drivers/megatec_usb.c --- drivers_orig/megatec_usb.c 1970-01-01 03:00:00.000000000 +0300 +++ drivers/megatec_usb.c 2006-12-04 13:05:55.000000000 +0300 @@ -0,0 +1,296 @@ +/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: t; -*- + * + * megatec_usb.c: usb communication layer for Megatec protocol based UPSes + * + * Copyright (C) Andrey Lelikov <nut-driver@lelik.org> + * + * megatec_usb.c created on 3-Oct-2006 + * + * 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 "megatec.h" +#include "libusb.h" + +#include <stdio.h> +#include <limits.h> +#include <string.h> + +/* + This is a communication driver for "USB HID" UPS-es which use proprietary +usb-to-serial converter and speak megatec protocol. Usually these are cheap +models and usb-to-serial converter is a huge oem hack - HID tables are bogus, +device has no UPS reports, etc. + This driver has a table of all known devices which has pointers to device- +specific communication functions (namely send a string to UPS and read a string +from it). Driver takes care of detection, opening a usb device, string +formatting etc. So in order to add support for another usb-to-serial device one +only needs to implement device-specific get/set functions and add an entry into +KnownDevices table. + +*/ + +static communication_subdriver_t *usb = &usb_subdriver; +static usb_dev_handle *udev=NULL; +static HIDDevice hiddevice; + +static int comm_usb_recv(char *buffer,size_t buffer_len,char endchar,const char *ignchars); + +typedef struct +{ + uint16_t vid; + uint16_t pid; + int (*get_data)(char *buffer,int buffer_size); + int (*set_data)(const char *str); +} usb_ups_t; + +usb_ups_t *usb_ups_device = NULL; + +/* + All devices known to this driver go here + along with their set/get routines +*/ + +static int get_data_agiler(char *buffer,int buffer_size); +static int set_data_agiler(const char *str); + +static usb_ups_t KnownDevices[]={ + { 0x05b8, 0x0000, get_data_agiler, set_data_agiler }, + { .vid=0 } /* end of list */ +}; + +static int comm_usb_match(HIDDevice *d, void *privdata) +{ + usb_ups_t *p; + + for (p=KnownDevices;p->vid!=0;p++) + { + if ( (p->vid==d->VendorID) && (p->pid==d->ProductID) ) + { + usb_ups_device = p; + return 1; + } + } + + p = (usb_ups_t*)privdata; + + if (NULL!=p) + { + if ( (p->vid==d->VendorID) && (p->pid==d->ProductID) ) + { + usb_ups_device = p; + return 1; + } + } + + return 0; +} + +static int comm_usb_open(const char *param) +{ + HIDDeviceMatcher_t match; + static usb_ups_t param_arg; + const char* p; + int ret,i; + union _u { + unsigned char report_desc[4096]; + char flush_buf[256]; + } u; + + memset(&match,0,sizeof(match)); + match.match_function = &comm_usb_match; + + if (0!=strcmp(param,"auto")) + { + param_arg.vid = (uint16_t) strtoul(param,NULL,16); + p = strchr(param,':'); + if (NULL!=p) + { + param_arg.pid = (uint16_t) strtoul(p+1,NULL,16); + } else { + param_arg.vid = 0; + } + + // pure heuristics - assume this unknown device speaks agiler protocol + param_arg.get_data = get_data_agiler; + param_arg.set_data = set_data_agiler; + + if (0!=param_arg.vid) + { + match.privdata = ¶m_arg; + } else { + upslogx(LOG_ERR, + "comm_usb_open: invalid usb device specified, must be \"auto\" or \"vid:pid\""); + return -1; + } + } + + ret = usb->open(&udev,&hiddevice,&match,u.report_desc,MODE_OPEN); + if (ret<0) + return ret; + + // flush input buffers + for (i=0;i<10;i++) + { + if (comm_usb_recv(u.flush_buf,sizeof(u.flush_buf),0,NULL)<1) break; + } + + return 0; +} + +static void comm_usb_close(const char *param) +{ + usb->close(udev); +} + +static int comm_usb_send(const char *fmt,...) +{ + char buf[128]; + size_t len; + va_list ap; + + if (NULL==udev) + return -1; + + va_start(ap, fmt); + + len = vsnprintf(buf, sizeof(buf), fmt, ap); + + va_end(ap); + + if ((len < 1) || (len >= (int) sizeof(buf))) + { + upslogx(LOG_WARNING, "comm_usb_send: vsnprintf needed more " + "than %d bytes", (int)sizeof(buf)); + buf[sizeof(buf)-1]=0; + } + + return usb_ups_device->set_data(buf); +} + +static int comm_usb_recv(char *buffer,size_t buffer_len,char endchar,const char *ignchars) +{ + int len; + char *src,*dst,c; + + if (NULL==udev) + return -1; + + len = usb_ups_device->get_data(buffer,buffer_len); + if (len<0) + return len; + + dst = buffer; + + for (src=buffer;src!=(buffer+len);src++) + { + c = *src; + + if ( (c==endchar) || (c==0) ) { + break; + } + + if (NULL!=strchr(ignchars,c)) continue; + + *(dst++) = c; + } + + // terminate string if we have space + if (dst!=(buffer+len)) + { + *dst = 0; + } + + return (dst-buffer); +} + +static megatec_comm_t comm_usb = +{ + .name = "usb", + .open = &comm_usb_open, + .close = &comm_usb_close, + .send = &comm_usb_send, + .recv = &comm_usb_recv +}; + +megatec_comm_t *comm = & comm_usb; + + +/************** minidrivers go after this point **************************/ + + +/* + Agiler seraial-to-usb device. + + Protocol was reverse-engineered from Windows driver + HID tables are complitely bogus + Data is transferred out as one 8-byte packet with report ID 0 + Data comes in as 6 8-byte reports per line , padded with zeroes + All constants are hardcoded in windows driver +*/ + +#define AGILER_REPORT_SIZE 8 +#define AGILER_REPORT_COUNT 6 +#define AGILER_TIMEOUT 5000 + +static int set_data_agiler(const char *str) +{ + unsigned char report_buf[AGILER_REPORT_SIZE]; + + if (strlen(str)>AGILER_REPORT_SIZE) + { + upslogx(LOG_ERR, + "set_data_agiler: output string too large"); + return -1; + } + + memset(report_buf,0,sizeof(report_buf)); + memcpy(report_buf,str,strlen(str)); + + return usb->set_report(udev,0,report_buf,sizeof(report_buf)); +} + +static int get_data_agiler(char *buffer,int buffer_size) +{ + int i,len; + char buf[AGILER_REPORT_SIZE*AGILER_REPORT_COUNT+1]; + + memset(buf,0,sizeof(buf)); + + for (i=0;i<AGILER_REPORT_COUNT;i++) + { + len = usb->get_interrupt(udev,buf+i*AGILER_REPORT_SIZE,AGILER_REPORT_SIZE,AGILER_TIMEOUT); + if (len!=AGILER_REPORT_SIZE) { + if (len<0) len=0; + buf[i*AGILER_REPORT_SIZE+len]=0; + break; + } + } + + len = strlen(buf); + + if (len > buffer_size) + { + upslogx(LOG_ERR, + "get_data_agiler: input buffer too small"); + len = buffer_size; + } + + memcpy(buffer,buf,len); + return len; +} + +/* EOF - megatec_usb.c */
> I just dont want the results of my work to disappear into void. Is > it something that can go into main tree? Please let me know what you > think. I can also do all the grunt work (write man page, add to a list > of supported devices, change debian USB hotplug script, etc) if this > driver will be included. Thanks.Your effort to support this UPS are appreciated. The only remark I want to make, is that I don't think it is needed to change the present megatec driver and preferably, I would refrain from doing so. The only thing needed, is a new serial_usb.c that provides (a subset of) the standard serial I/O functions with the same name. Just ignore the parameters that are not used and provide stubs for the functions not implemented. With a bit of luck, we might be able to reuse that for other UPS'es that use similar hardware, but speak a different protocol. Then at link time you can decide whether to use the serial.o or serial_usb.o object to decide for what kind of interface you're building. The only issue that remains now, is that the driver should report at startup which version it is. This can be handled nicely by using the value of 'progname' in the upsdrv_banner() function in the megatec.c file. Yesterday I committed a change to make the value of this parameter available at the time upsdrv_banner() is called. Best regards, Arjen -- Eindhoven - The Netherlands Key fingerprint - 66 4E 03 2C 9D B5 CB 9B 7A FE 7E C1 EE 88 BC 57
Hi Andrey, thanks for your reverse-engineering efforts! Could you please post the HID descriptor from this device? What is the OEM mechanism that you discovered? It would be nice if you could document the serial-over-USB protocol used. We have in the past seen several different USB devices that were not proper HID devices, and at least one of them (the Krauler) seemed to speak a USB-to-serial version of the megatec protocol. They all had (almost) identical HID descriptors. So far none of them are supported by NUT, but there is a good chance that your driver can support them, if the OEM hack is indeed the same. The devices are: * Krauler UP-M500VA (see Alexander I. Gordeev's thread on nut-upsdev, November 2006) * Ablerex 625L (see Lau Kim Ping's thread on nut-upsuser, October 2006) * Atlantis-Land S1501 (made by Ablerex) (see ngpost1's threads on nut-upsuser and nut-upsdev, December 2005) * Belkin F6H500ukUNV (made by MEC?) (see the thread by meherenow, spamwhole, and Robert Kent on nut-upsdev, September 2006) I suspect that all of these devices use the same hardware and protocol. If your OEM mechanism is the same as the Krauler's, then we should distribute your driver to the owners of the above devices and see if they get it to work. It would be great if this family of devices could be supported. -- Peter P.S. accommodating your driver in Makefile.am is not a big problem. Several drivers already have specialized LDADD variables, including all of the USB drivers (which don't link against serial.o). Andrey Lelikov wrote:> > > Hello, all. > > Some time ago I bought myself a UPS which was advertised as USB-HID. > It was a surprise to learn that while it definitely is recognized as USB > device, the HID descriptor has no UPS pages at all. The only software it > came with was a windows program written in visual basic. Trying to > research this topic I found a reference to energizer USB ups driver and > learned about variety of UPSes that speak megatec protocol and have > proprietary usb-to-serial connectors. The energizer driver did not work > for me - my converter was different. > > The usb-serial chip used in my UPS (SVEN Pro+ usb series) is a > complete OEM hack. Apparently the only reason it is a HID device is so > usermode application can access it without driver in windows. Its HID > tables are flawed, it is impossible to learn a valid report size from > it. After some reverse-engineering of windows driver I learned that all > these constants were hardcoded in driver directly. > > In order to write a driver for it I had two options -- either add > support to energizer driver or add another communication layer to > megatec protocol. I did not like energizer driver approach - the > energizer driver is a hack by itself, does not support all of megatec > protocol (and especially assumes that UPS is 110V rated rather then use > F command), and uses hiddev. In my case there was no way to use a hiddev > (tables ar flawed) and anyways libusb seemed to be much more robust > alternative. I choose to change megatec driver. Since usb drivers go to > separate package I creates a new driver megatec_usb . The changes in > megatec.c were minimal - only replace all serial send/getline with calls > to a communication layer and change two lines in init/cleanup. Now > megatec.c links with megatec_ser.c that impements serial communication > layer to produce old megatec driver, and > megatec.c+megatec_usb.c+libusb.c produce megatec_usb driver. > > (this is a good point to actually look at the attached patch) > > The architecture of megatec_usb itself is modular. For each device > it knows about there are two routines (send/recv) that do actual > transfer. This way in order to support yet another usb-serial controller > one only needs to add corresponding functions and an entry into device > table. For now the driver supports only single chip by agiler that > happen to be in my UPS. It might be that this driver will support > energizer UPSes too, but naturally I have no chance to test that. Anyway > changing it to support all hardware that is supported by energizer > driver should be trivial - this way energizer driver (the only remaining > "old" hid driver) may be replaced by megatec_usb. > > While changes to megatec.c are marginal and megatec_ser.c is a > straightforward one, i did not test it at all. megatec_usb works fine on > my system (amd64 debian etch) for several days. So someone with an > serial megatec UPS have to look at it, or at least code review it. Also > it probably will make sense to make a call for users of such UPSes and > try to see if that hardware will work with this driver. > > I just dont want the results of my work to disappear into void. Is > it something that can go into main tree? Please let me know what you > think. I can also do all the grunt work (write man page, add to a list > of supported devices, change debian USB hotplug script, etc) if this > driver will be included. Thanks.
spamwhole@gmail.com
2006-Dec-05 21:16 UTC
[Nut-upsdev] megatec over USB - new driver patch
The driver can be downloaded from: http://www.belkin.com/support/download.asp?lang=1&download=F6H500ukUNV&mode= Andrey Lelikov writes:> Robert Kent wrote: >>> * Belkin F6H500ukUNV (made by MEC?) (see the thread by meherenow, >>> spamwhole, and Robert Kent on nut-upsdev, September 2006) >>> >> If I can help in anyway please let me know. >> >> > Can you please send me windows driver/software that came with this UPS?