[Nut-upsdev] megatec over USB - new driver patch

Andrey Lelikov
Tue, 05 Dec 2006 13:40:45 -0000

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 = &param_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 */