Voltage override in megatec and megatec-over-usb [was: Re:
[Nut-upsdev] nut-2.0.5 megatec + Online Xanto]
Alexander I. Gordeev
lasaine at lvk.cs.msu.su
Tue Jan 23 02:31:02 CET 2007
On Tue, 23 Jan 2007 03:58:35 +0300, Carlos Rodrigues <carlos.efr at 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 at lvk.cs.msu.su>
+ * Based upon megatec_usb.c: Copyright (C) Andrey Lelikov <nut-driver at 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
More information about the Nut-upsdev
mailing list