Carlos Rodrigues
2007-Jan-23 01:58 UTC
Voltage override in megatec and megatec-over-usb [was: Re: [Nut-upsdev] nut-2.0.5 megatec + Online Xanto]
On 1/23/07, Henning Brauer <hb-nut@bsws.de> wrote:> good new first: the megatec driverin 2.0.5 now works with the Online > Xanto S3000R here - well, for the very basics. > > the UPS has NO way ofidentifying itself. It also does not respond to > the power ratings query ("F"). I previously used a hacked up fentonups > driver. This means the driver cannot figure out the battery voltage and > thus no battery charge level, which is a problem in our setup.You have to remember that the charge calculation in megatec is very crude. The fact that it drops abruptly when the UPS goes on battery is the most evident symptom. And there isn't any way around this. Not any feasible one, that is. So, if you are depending on the charge value for something important, think twice about it.> it is a 96V unit. the values I had in fentonups.h were > + { "XS3000R", "Online", "Xanto S3000R", > + 1.81, 0.44, 1.81, 0.44, 165, 200, 240, 275, 1 }, > if there were a way to give the driver the desired voltage overriding > the auto-detection, everything would work fine again...I had this > hacked up for 2.0.3 at one point, but can't fine it right now. Carlos, > didn't I mail hat before? in any case. does anyone have a better idea > to solve this?The version in the trunk has an option to do this ("battvolts=XX:XX") since yesterday.You can get megatec.c and drop it over the 2.0.5 sources, it should build just fine. It isn't in 2.0.5 because I didn't want to do it unless I was sure there was no other way, and an option in a stable version is an option that people may start using and thus can't be removed easily later on. But my Mustek PowerMust broke, and I got a PowerWalker model that looks just like the old one (software-wise, that is) but where the charge gets calculated wrong. Talk about cheap hardware... BTW, this model has an USB port along the RS232 one. So... I can help if the project to create a "usbserial.o" communications layer picks up. (If it doesn't, I will think about doing it on my own, based on the code posted here a while ago, but I promise nothing. My time is currently in short supply and I'm a complete newbie to this USB protocol stuff.) -- Carlos Rodrigues
Henning Brauer
2007-Jan-23 02:12 UTC
Voltage override in megatec and megatec-over-usb [was: Re: [Nut-upsdev] nut-2.0.5 megatec + Online Xanto]
* Carlos Rodrigues <carlos.efr@mail.telepac.pt> [2007-01-23 01:58]:> On 1/23/07, Henning Brauer <hb-nut@bsws.de> wrote: > >good new first: the megatec driverin 2.0.5 now works with the Online > >Xanto S3000R here - well, for the very basics. > > > >the UPS has NO way ofidentifying itself. It also does not respond to > >the power ratings query ("F"). I previously used a hacked up fentonups > >driver. This means the driver cannot figure out the battery voltage and > >thus no battery charge level, which is a problem in our setup. > > You have to remember that the charge calculation in megatec is very > crude. The fact that it drops abruptly when the UPS goes on battery is > the most evident symptom. And there isn't any way around this. Not any > feasible one, that is. > > So, if you are depending on the charge value for something important, > think twice about it.I only really care about a rough guess. heck, most of the time I really only care about "about full" and "not" :)> >it is a 96V unit. the values I had in fentonups.h were > >+ { "XS3000R", "Online", "Xanto S3000R", > >+ 1.81, 0.44, 1.81, 0.44, 165, 200, 240, 275, 1 }, > >if there were a way to give the driver the desired voltage overriding > >the auto-detection, everything would work fine again...I had this > >hacked up for 2.0.3 at one point, but can't fine it right now. Carlos, > >didn't I mail hat before? in any case. does anyone have a better idea > >to solve this? > > The version in the trunk has an option to do this ("battvolts=XX:XX") > since yesterday.You can get megatec.c and drop it over the 2.0.5 > sources, it should build just fine.thatis excellent news.> It isn't in 2.0.5 because I didn't want to do it unless I was sure > there was no other way, and an option in a stable version is an option > that people may start using and thus can't be removed easily later on. > But my Mustek PowerMust broke, and I got a PowerWalker model that > looks just like the old one (software-wise, that is) but where the > charge gets calculated wrong. Talk about cheap hardware...I refrained from buying more UPSes from Online for a reason too :)> BTW, this model has an USB port along the RS232 one. So... I can help > if the project to create a "usbserial.o" communications layer picks > up. (If it doesn't, I will think about doing it on my own, based on > the code posted here a while ago, but I promise nothing. My time is > currently in short supply and I'm a complete newbie to this USB > protocol stuff.)don't have any USB units (and the sun ultra1 used for UPS monitoring here doesn't have usb either) -- Henning Brauer, hb@bsws.de, henning@openbsd.org BS Web Services, http://bsws.de Full-Service ISP - Secure Hosting, Mail and DNS Services Dedicated Servers, Rootservers, Application Hosting - Hamburg & Amsterdam
Alexander I. Gordeev
2007-Jan-23 02:47 UTC
Voltage override in megatec and megatec-over-usb [was: Re: [Nut-upsdev] nut-2.0.5 megatec + Online Xanto]
On Tue, 23 Jan 2007 03:58:35 +0300, Carlos Rodrigues <carlos.efr@mail.telepac.pt> wrote:> BTW, this model has an USB port along the RS232 one. So... I can help > if the project to create a "usbserial.o" communications layer picks > up. (If it doesn't, I will think about doing it on my own, based on > the code posted here a while ago, but I promise nothing. My time is > currently in short supply and I'm a complete newbie to this USB > protocol stuff.) >I have done some rework of Andrey Lelikov's serial-over-usb code. It is almost complete, but I need to work further to fully support my own UPS (Krauler). I recommend not to use it with this model al all. Agiler UPSes are supported by the original Andrey's code. But I think the generic usb-serial layer is almost ready to work. So only writing a subdriver for your UPS is needed. Unfortunately I don't have much time at the moment to finish the work. I think I'll continue working on it in February. Maybe the current code will be useful for you: diff -Naur drivers.orig/Makefile.am drivers/Makefile.am --- drivers.orig/Makefile.am 2007-01-23 04:21:10.000000000 +0300 +++ drivers/Makefile.am 2007-01-23 04:21:02.000000000 +0300 @@ -19,7 +19,7 @@ mge-shut mge-utalk newmge-shut nitram oneac optiups powercom rhino \ safenet skel solis tripplite tripplitesu upscode2 victronups powerpanel SNMP_DRIVERLIST = snmp-ups -USB_LIBUSB_DRIVERLIST = usbhid-ups bcmxcp_usb tripplite_usb +USB_LIBUSB_DRIVERLIST = usbhid-ups bcmxcp_usb tripplite_usb megatec_usb USB_HIDDEV_DRIVERLIST = energizerups USB_DRIVERLIST = $(USB_LIBUSB_DRIVERLIST) $(USB_HIDDEV_DRIVERLIST) HAL_DRIVERLIST = hald-addon-usbhid-ups hald-addon-bcmxcp_usb hald-addon-tripplite_usb @@ -128,6 +128,10 @@ bcmxcp_usb_CFLAGS = $(AM_CFLAGS) $(LIBUSB_CFLAGS) bcmxcp_usb_LDADD = $(LDADD_DRIVERS) $(LIBUSB_LDFLAGS) +megatec_usb_SOURCES = megatec.c serial_usb_megatec.c libusb.c +megatec_usb_CFLAGS = $(AM_CFLAGS) $(LIBUSB_CFLAGS) +megatec_usb_LDADD = $(LDADD_DRIVERS) $(LIBUSB_LIBS) + # HID-over-serial newmge_shut_SOURCES = usbhid-ups.c libshut.c libhid.c hidparser.c mge-hid.c newmge_shut_CFLAGS = $(AM_CFLAGS) -DSHUT_MODE diff -Naur drivers.orig/serial_usb_megatec.c drivers/serial_usb_megatec.c --- drivers.orig/serial_usb_megatec.c 1970-01-01 03:00:00.000000000 +0300 +++ drivers/serial_usb_megatec.c 2007-01-23 04:18:15.000000000 +0300 @@ -0,0 +1,363 @@ +/* serial_usb_megatec.c - usb communication layer for Megatec protocol based UPSes + * + * Copyright (C) 2007 Alexander Gordeev <lasaine@lvk.cs.msu.su> + * Based upon megatec_usb.c: Copyright (C) Andrey Lelikov <nut-driver@lelik.org> + * + * 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 "serial.h" + +#include <stdio.h> +#include <limits.h> +#include <string.h> +#include <stdlib.h> +#include <usb.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; + +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 int get_data_krauler(char *buffer,int buffer_size); +static int set_data_krauler(const char *str); + +static usb_ups_t KnownDevices[]={ + { 0x05b8, 0x0000, get_data_agiler, set_data_agiler }, + { 0x0001, 0x0000, get_data_krauler, set_data_krauler }, + { .vid=0 } /* end of the list */ +}; + +// TODO: Fix matching non-auto selected devices +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 void usb_open_error(const char *port) +{ + exit(EXIT_FAILURE); +} + +int ser_open(const char *port) +{ + 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 != strcasecmp(port,"auto")) + { + param_arg.vid = (uint16_t) strtoul(port,NULL,16); + p = strchr(port,':'); + 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, + "ser_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) + usb_open_error(port); + + /* flush input buffers */ + for (i=0;i<10;i++) + { + if (ser_get_line(upsfd, u.flush_buf, sizeof(u.flush_buf), 0, + NULL, 0, 0)<1) + break; + } + + return 0; +} + +int ser_set_speed(int fd, const char *port, speed_t speed) +{ + return 0; +} + +int ser_close(int fd, const char *port) +{ + usb->close(udev); + return 0; +} + +unsigned int ser_send_pace(int fd, unsigned long d_usec, 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, "ser_send_pace: vsnprintf needed more " + "than %d bytes", (int)sizeof(buf)); + buf[sizeof(buf)-1]=0; + } + + return usb_ups_device->set_data(buf); +} + +int ser_get_line(int fd, char *buf, size_t buflen, char endchar, + const char *ignset, long d_sec, long d_usec) +{ + int len; + char *src,*dst,c; + + if (NULL==udev) + return -1; + + len = usb_ups_device->get_data(buf,buflen); + if (len<0) + return len; + + dst = buf; + + for (src=buf;src!=(buf+len);src++) + { + c = *src; + + if ( c==endchar ) + break; + + if ( (c==0) || ( strchr(ignset,c)!=NULL ) ) + continue; + + *(dst++) = c; + } + + /* terminate string if we have space */ + if (dst!=(buf+len)) + *dst = 0; + + return (dst-buf); +} + +/************** minidrivers go after this point **************************/ + + +/* + Agiler serial-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; +} + + +/* + Krauler serial-to-usb device. + + Protocol was reverse-engineered using Windows driver +*/ + +#define KRAULER_COMMAND_BUFFER_SIZE 9 +#define KRAULER_TIMEOUT 5000 + +static char krauler_command_buffer[KRAULER_COMMAND_BUFFER_SIZE]; + +static int set_data_krauler(const char *str) +{ + int len; + + len = strlen(str); + if (len >= KRAULER_COMMAND_BUFFER_SIZE) + { + upslogx(LOG_ERR, + "set_data_krauler: output string too large"); + return -1; + } + + krauler_command_buffer[len] = 0; + memcpy(krauler_command_buffer,str,len); + + return len; +} + +static int get_data_krauler(char *buffer,int buffer_size) +{ + int res = 0; + int i; + + if (strcmp(krauler_command_buffer, "Q1\r") == 0) + { + res = usb_get_descriptor(udev, USB_DT_STRING, 0x03, buffer, buffer_size); + /*res = usb_control_msg(udev, USB_ENDPOINT_IN+1, USB_REQ_GET_DESCRIPTOR, + (USB_DT_STRING << 8) + 3, 0, buffer, 0x9, USB_TIMEOUT);*/ + } + + if (strcmp(krauler_command_buffer, "I\r") == 0) + { + res = usb_get_descriptor(udev, USB_DT_STRING, 0x0c, buffer, buffer_size); + /*res = usb_control_msg(udev, USB_ENDPOINT_IN+1, USB_REQ_GET_DESCRIPTOR, + (USB_DT_STRING << 8) + 3, 0, buffer, 0x9, USB_TIMEOUT);*/ + } + + if (strcmp(krauler_command_buffer, "F\r") == 0) + { + res = usb_get_descriptor(udev, USB_DT_STRING, 0x0d, buffer, buffer_size); + /*res = usb_control_msg(udev, USB_ENDPOINT_IN+1, USB_REQ_GET_DESCRIPTOR, + (USB_DT_STRING << 8) + 3, 0, buffer, 0x9, USB_TIMEOUT);*/ + } + + if(res >= 4) + memset(buffer,0,4); + + krauler_command_buffer[0] = 0; + return res; +} -- Alexander