]> git.webhop.me Git - lcd4linux.git/commitdiff
plugin_huawei from Jar (forgot to add it last time)
authormichael <michael@3ae390bd-cb1e-0410-b409-cd5a39f66f1f>
Sat, 30 Jan 2010 08:15:04 +0000 (08:15 +0000)
committermichael <michael@3ae390bd-cb1e-0410-b409-cd5a39f66f1f>
Sat, 30 Jan 2010 08:15:04 +0000 (08:15 +0000)
git-svn-id: https://ssl.bulix.org/svn/lcd4linux/trunk@1097 3ae390bd-cb1e-0410-b409-cd5a39f66f1f

plugin_huawei.c [new file with mode: 0644]

diff --git a/plugin_huawei.c b/plugin_huawei.c
new file mode 100644 (file)
index 0000000..9a64bda
--- /dev/null
@@ -0,0 +1,1199 @@
+/* $Id: plugin_huawei.c 870 2008-04-10 14:55:23Z michux $
+ * $URL: https://ssl.bulix.org/svn/lcd4linux/trunk/plugin_huawei.c $
+ *
+ * plugin plugin_huawei. Copyright (C) 2010 Jar <jar@pcuf.fi>
+ *
+ * Huawei E220 3G modem statistics via /dev/ttyUSB* user interface. It may (or may not) work well
+ * with other Huawei 3G USB modems too (which uses PPP interface between modem and computer), like
+ * E160, E169, E226, E272, E230 etc. Since they all seem to use same kind of set AT commands and
+ * responses. Tested with Huawei E220, E160E  and Vodafone Huawei K3565.
+ *
+ * Thanks to Mikko <vmj@linuxbox.fi> for contributing the scan_uint() function and some other code.
+ *
+ * Based on sample plugin which is
+ * Copyright (C) 2003 Michael Reinelt <michael@reinelt.co.at>
+ * Copyright (C) 2004, 2005, 2006, 2007, 2008 The LCD4Linux Team <lcd4linux-devel@users.sourceforge.net>
+ *
+ *
+ * This file is part of LCD4Linux.
+ *
+ * LCD4Linux 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, or (at your option)
+ * any later version.
+ *
+ * LCD4Linux 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+/* 
+ * exported functions:
+ *
+ * huawei::quality('%'|'dbm'|'rssi')
+ * huawei::mode('text'|'number')
+ * huawei::manuf()
+ * huawei::model()
+ * huawei::fwver()
+ * huawei::operator()
+ * huawei::flowreport('uptime'|'uptime_seconds'|'tx_rate'|'rx_rate'|'total_tx'|'total_rx')
+ *
+ * With '%' parameter, you can get the percentage (0...100%) value of RSSI (received signal strength).
+ * With 'dbm' parameter, you can get the dbm value of RSSI.
+ * With 'rssi' parameter, you can get the relative RSSI value 0...31.
+ *
+ * With 'text' parameter, you can get the mode value as text, like 'HSDPA'.
+ * With 'number' parameter, you can get the mode value as number, like 5.
+ *
+ * With 'uptime' parameter, you can get the uptime (connection time) value as text, like '1 days 10:11:12'.
+ * With 'uptime_seconds' parameter, you can get the uptime (connection time) seconds as number, like 12345.
+ *
+ * This plugin tries to connect per default to the Huawei user interface device at /dev/ttyUSB1.
+ * If your modem is connected to another device you can use the environment variable HUAWEI_PORT.
+ * For example export HUAWEI_PORT=/dev/ttyUSB2
+ *
+ */
+
+
+/* define the include files you need */
+#include "config.h"
+
+#include <stdio.h>             /* standard buffered input/output */
+#include <stdlib.h>            /* standard library definitions */
+#include <stdarg.h>            /* for va_start et al */
+#include <string.h>            /* string operations */
+#include <unistd.h>            /* read(), close() */
+#include <fcntl.h>             /* open() */
+#include <termios.h>           /* tcflush() */
+#include <sys/types.h>         /* data types */
+#include <sys/stat.h>          /* stat structure */
+#include <sys/time.h>          /* timeval structure */
+#include <errno.h>             /* system error numbers */
+
+
+/* these should always be included */
+#include "debug.h"
+#include "plugin.h"
+
+
+#ifdef WITH_DMALLOC
+#include <dmalloc.h>
+#endif
+
+
+#define PORT "/dev/ttyUSB1"    /* default port */
+#define BAUDRATE B38400                /* default baudrate */
+#define SEND_BUFFER_SIZE 128   /* send buffer size */
+#define RECV_BUFFER_SIZE 256   /* receive buffer size */
+#define MIN_INTERVAL 100       /* minimum query interval 100 ms */
+
+
+#define INIT_STRING    "ATE1;^CURC=0;^DSFLOWCLR"       /* disable unsolicited report codes and reset DS traffic
+                                                        * with local echo enabled 
+                                                        */
+#define QUALITY                "ATE1;+CSQ"     /* signal quality query with local echo enabled */
+#define SYSINFO                "ATE1;^SYSINFO" /* network access query with local echo enabled */
+#define MANUF          "ATE1;+GMI"     /* manufacturer query with local echo enabled */
+#define MODEL          "ATE1;+GMM"     /* model query with local echo enabled */
+#define FWVER          "ATE1;+CGMR"    /* firmware version query with local echo enabled */
+#define FLOWREPORT     "ATE1;^DSFLOWQRY"       /* DS traffic query with local echo enabled */
+#define OPERATOR       "ATE1;+COPS=3,0;+COPS?" /* gsm/umts operator query (3=set format only, 0=long alphanum. string)
+                                                * with local echo enabled
+                                                */
+
+static char name[] = "plugin_huawei.c";
+
+static char *sub_system_mode[] = {
+    "NO CONN",                 /* no service */
+    "GSM",                     /* 2G/GSM */
+    "GRPS",                    /* 2.5G/GPRS */
+    "EDGE",                    /* 2.75G/EDGE */
+    "WCDMA",                   /* 3G/UMTS */
+    "HSDPA",                   /* 3.5G/UMTS */
+    "HSUPA",                   /* 3.5G/UMTS */
+    "HSPA",                    /* HSDPA+HSUPA */
+    "UNKNOWN"
+};
+
+static int fd = -2;            /* serial fd */
+static char *port = NULL;      /* serial device */
+
+/* signal strength query */
+static unsigned int rssi = 0;  /* relative rssi 0...31 */
+static unsigned int ber = 0;   /* ber (bit error rate, not supported) */
+
+/* manufacturer query */
+static char manuf[32] = "";    /* manufacturer */
+
+/* model query */
+static char model[32] = "";    /* model */
+
+/* fw version query */
+static char fwver[32] = "";    /* firmware version */
+
+/* gsm/umts operator query */
+static char operator[64] = ""; /* current gsm/umts network operator */
+
+/* DS traffic report query */
+static unsigned long int last_ds_time = 0;     /* last DS connection time [s] */
+static unsigned long long int last_tx_flow = 0;        /* last DS transmiting trafic [Bytes] */
+static unsigned long long int last_rx_flow = 0;        /* last DS receiving trafic [Bytes] */
+static unsigned long int total_ds_time = 0;    /* total DS connection time [s] */
+static unsigned long long int total_tx_flow = 0;       /* total transmitting DS traffic [Bytes] */
+static unsigned long long int total_rx_flow = 0;       /* total receiving DS traffic [Bytes] */
+
+static double calc_tx_rate = 0;        /* calculated tx rate */
+static double calc_rx_rate = 0;        /* calculated rx rate */
+
+/* system information query */
+static unsigned int status = 0;        /* system service state */
+static unsigned int domain = 0;        /* system service domain */
+static unsigned int roaming_status = 0;        /* roaming status */
+static unsigned int mode = 0;  /* system mode */
+static unsigned int sim_state = 0;     /* SIM card state */
+static unsigned int reserved = 0;      /* reserved, E618 used it to indicate the simlock state */
+static unsigned int sub_mode = 0;      /* system sub mode */
+
+static int debug = 0;          /* enable debug messages */
+
+
+static int age_diff(struct timeval prev_age)
+{
+    int diff;
+    struct timeval now;
+
+    gettimeofday(&now, NULL);
+    diff = (now.tv_sec - prev_age.tv_sec) * 1000 + (now.tv_usec - prev_age.tv_usec) / 1000;
+
+    return diff;
+}
+
+static int scan_uint(const char *str, unsigned int nargs, ...)
+{
+    unsigned int wall = 0;
+    unsigned int i = 0;                /* number of parsed numbers */
+    unsigned int *a = NULL;    /* pointer to currently parsed number (type
+                                  must be that of the actual arguments) */
+
+    /* initialize the argument list */
+    va_list argv;
+    va_start(argv, nargs);
+
+    if (nargs > 0) {
+       /* initialize to first argument */
+       a = va_arg(argv, unsigned int *);
+       /* initialize it to zero */
+       *a = 0;
+    }
+
+    /* loop through the argument list and the string */
+    while (str && *str && wall < 1024) {
+       switch (*str) {
+       case '1':
+       case '2':
+       case '3':
+       case '4':
+       case '5':
+       case '6':
+       case '7':
+       case '8':
+       case '9':
+       case '0':
+           /* intent: multiply current value of 'a' with 10 and add
+              numeric value of character str) */
+           if (i < nargs) {
+               *a = ((*a) * 10) + ((*str) - '0');
+           }
+           break;
+
+       case ',':
+           if (++i < nargs) {
+               /* proceed to next argument */
+               a = va_arg(argv, unsigned int *);
+               /* initialize it to zero */
+               *a = 0;
+           } else {
+               /* this was the last number to parse */
+               /* (comma indicates that there are more) */
+           }
+           break;
+
+       default:
+           /* ignore any unknown characters */
+           break;
+       }
+
+       /* proceed to next character */
+       str++;
+       wall++;
+    }
+
+    /* fill any extraneous arguments with zero */
+    if (str && !(*str) && i < nargs - 1) {
+       unsigned int j = i;
+       for (j++; j < nargs; j++) {
+           a = va_arg(argv, unsigned int *);
+           *a = 0;
+       }
+    }
+
+    /* finalize the argument list */
+    va_end(argv);
+
+    /* return the number of parsed numbers */
+    return (int) (i > 0) ? i + 1 : i;
+}
+
+static int huawei_port_exists(const char *device)
+{
+    int ret;
+    struct stat stat_ptr;
+
+    /* check the device is present */
+    ret = stat(device, &stat_ptr);
+    if (ret < 0)
+       return 0;
+
+    /* check the device is character device */
+    return (S_ISCHR(stat_ptr.st_mode));
+}
+
+static void huawei_read_port(void)
+{
+    char *test;
+
+    /* port can be defined via env variable */
+    if ((test = getenv("HUAWEI_PORT"))) {
+       if (port != test) {
+           port = test;
+           info("%s: Using HUAWEI_PORT=%s from env variable for user interface device", name, port);
+       }
+    } else
+       port = PORT;
+
+    return;
+}
+
+static int huawei_configure_port(void)
+{
+    int ret;
+    struct termios options;
+
+    /* open port */
+    fd = open(port, O_RDWR | O_NOCTTY | O_NDELAY);
+    if (fd < 0) {
+       error("%s: ERROR: Problem opening %s. Try using the HUAWEI_PORT env variable (export HUAWEI_PORT=/dev/ttyUSB*)",
+             name, port);
+       return -1;
+    }
+
+    /* enable blocking behavior with timeout */
+    ret = fcntl(fd, F_SETFL, 0);
+    if (ret < 0) {
+       error("%s: ERROR: Problem setting file descriptor status flags %i", name, fd);
+       close(fd);
+       fd = -2;
+       return -1;
+    }
+
+    /* get the current options */
+    ret = tcgetattr(fd, &options);
+    if (ret < 0) {
+       error("%s: ERROR: Problem getting terminal attributes %s", name, port);
+       close(fd);
+       fd = -2;
+       return -1;
+    }
+
+    /* set raw input */
+    options.c_cflag |= (CS8 | CLOCAL | CREAD);
+    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
+    options.c_iflag &= ~(IGNBRK | BRKINT | IGNPAR | IGNCR | INLCR | ICRNL |
+                        IUCLC | IXANY | IXON | IXOFF | INPCK | ISTRIP);
+    options.c_oflag = 0;       /* raw output */
+    options.c_cc[VMIN] = 0;
+    options.c_cc[VTIME] = 1;   /* 0.1 second timeout */
+
+    /* set the input baud rate to BAUDRATE */
+    ret = cfsetispeed(&options, BAUDRATE);
+    if (ret < 0) {
+       error("%s: ERROR: Problem setting input baud rate %s", name, port);
+       close(fd);
+       fd = -2;
+       return -1;
+    }
+
+    /* set the output baud rate to BAUDRATE */
+    ret = cfsetospeed(&options, BAUDRATE);
+    if (ret < 0) {
+       error("%s: ERROR: Problem setting output baud rate %s", name, port);
+       close(fd);
+       fd = -2;
+       return -1;
+    }
+
+    /* set the options */
+    ret = tcsetattr(fd, TCSANOW, &options);
+    if (ret < 0) {
+       error("%s: ERROR: Problem setting terminal attributes %s", name, port);
+       close(fd);
+       fd = -2;
+       return -1;
+    }
+
+    /* flush input and output buffers */
+    ret = tcflush(fd, TCIFLUSH);
+    if (ret < 0) {
+       error("%s: ERROR: Problem flushing input and output buffers %s", name, port);
+       close(fd);
+       fd = -2;
+       return -1;
+    }
+
+    return fd;
+}
+
+static int huawei_send(const char *cmd)
+{
+    int len, bytes;
+    char buf[SEND_BUFFER_SIZE];
+
+    sprintf(buf, "%s\r", cmd);
+    len = strlen(buf);
+
+    /* write */
+    bytes = write(fd, buf, len);
+
+    /* force null termination */
+    if (bytes > 0)
+       buf[bytes] = '\0';
+    else
+       buf[0] = '\0';
+
+    if (bytes < 0 && errno != EAGAIN) {
+       error("%s: ERROR: Writing to device %s failed: %s", name, port, strerror(errno));
+       return -1;
+    } else if (bytes != len) {
+       error("%s: ERROR: Partial write when writing to device %s", name, port);
+       return -1;
+    }
+
+    /* remove trailing CR */
+    while (bytes > 0 && buf[bytes - 1] == '\r') {
+       buf[--bytes] = '\0';
+    }
+
+    if (debug)
+       debug("DEBUG: <-Send bytes=%i, send buf=%s", bytes, buf);
+
+    return bytes;
+}
+
+static char *huawei_receive(void)
+{
+    int run, count, bytes;
+    char c[1], *p;
+    static char buf[RECV_BUFFER_SIZE];
+
+    /* skip the plain NL's and CR's (<CR><LF><CR><LF> makes max. 4 pcs. of them) and re-read */
+    for (run = 0; run < 4; run++) {
+
+       /* pointer to the current position in the buffer */
+       p = buf;
+
+       /* set count to buffer size */
+       count = RECV_BUFFER_SIZE;
+
+       /* Read until a NL or a CR is encountered. If 'buf' fills up before newline is seen,
+        * exit the loop when there's still room for zero termination (count == 1).
+        */
+       while (count > 1) {
+           bytes = read(fd, c, 1);
+
+           if (*c == '\n' || *c == '\r' || (bytes < 0 && errno != EAGAIN))
+               break;
+
+           *p = *c;
+           p++;
+           count--;
+       }
+
+       /* force null termination */
+       *p = '\0';
+
+       /* now we have data (when buf len > 0), exit from for-loop and return the data to caller */
+       if (strlen(buf) > 0 || (bytes < 0 && errno != EAGAIN))
+           break;
+    }
+
+    if (bytes < 0 && errno != EAGAIN) {
+       error("%s: ERROR: Reading from device %s failed: %s", name, port, strerror(errno));
+       return ("");
+    }
+
+    /* print bytes read and buffer data when debuging enabled */
+    if (debug)
+       debug("DEBUG: ->Received bytes=%i, receive buf=%s", strlen(buf), buf);
+
+    return (buf);
+}
+
+static int huawei_recv_error(const char *msg)
+{
+    if (strstr(msg, "ERROR") != NULL || strncmp(msg, "NO CARRIER", 10) == 0 ||
+       strncmp(msg, "COMMAND NOT SUPPORT", 19) == 0 || strncmp(msg, "TOO MANY PARAMETERS", 19) == 0)
+       return 1;
+
+    return 0;
+}
+
+static char *huawei_send_receive(const char *cmd)
+{
+    int run, bytes_send, recv_seq;
+    char *tmp;
+    static char reply[RECV_BUFFER_SIZE];
+
+    /* send command to modem */
+    bytes_send = huawei_send(cmd);
+
+    /* start with an empty reply */
+    strcpy(reply, "");
+
+    if (bytes_send > 0) {
+
+       recv_seq = 0;
+
+       /* receive modem responses (currently handle only 3 lines of responses:
+        * request->reply->ok|error)
+        */
+       for (run = 0; run < 3; run++) {
+
+           tmp = huawei_receive();
+
+           /* init cmd */
+           if (strncmp(cmd, INIT_STRING, strlen(INIT_STRING)) == 0) {
+
+               /* waiting local echo back after the init string has been sent */
+               if (recv_seq == 0 && strncmp(tmp, INIT_STRING, strlen(INIT_STRING)) == 0) {
+                   recv_seq = 1;
+               }
+
+               /* waiting "OK" after local echo has been received */
+               else if (recv_seq == 1 && strncmp(tmp, "OK", 2) == 0) {
+                   strncpy(reply, tmp, sizeof(reply) - 1);
+                   break;
+               }
+
+               /* waiting possible "ERROR" after local echo has been received */
+               else if (recv_seq == 1 && huawei_recv_error(tmp) == 1) {
+                   strncpy(reply, tmp, sizeof(reply) - 1);
+                   break;
+               }
+
+               /* data query cmd */
+           } else {
+
+               /* waiting local echo back after the cmd has been sent */
+               if (recv_seq == 0 && strncmp(tmp, cmd, strlen(cmd)) == 0) {
+                   recv_seq = 1;
+               }
+
+               /* waiting data (but not "OK" or "ERROR") after local echo has been received */
+               else if (recv_seq == 1 && strlen(tmp) > 0 && huawei_recv_error(tmp) == 0) {
+                   recv_seq = 2;
+                   strncpy(reply, tmp, sizeof(reply) - 1);
+               }
+
+               /* waiting "OK" after data has been received */
+               else if (recv_seq == 2 && strncmp(tmp, "OK", 2) == 0) {
+                   break;
+               }
+
+               /* waiting possible "ERROR" after local echo has been received */
+               else if (recv_seq == 1 && huawei_recv_error(tmp) == 1) {
+                   strncpy(reply, tmp, sizeof(reply) - 1);
+                   break;
+               }
+           }
+       }
+    }
+
+    return (reply);
+}
+
+static int huawei_configured(void)
+{
+    int port_exists, ret, bytes;
+    static int connected = -1, configured = 0;
+    char *buf;
+
+    /* re-read port because device name may change during plugin execution */
+    huawei_read_port();
+
+    /* check the device exists before reading on it */
+    port_exists = huawei_port_exists(port);
+
+    /* modem removed event */
+    if (port_exists < 1 && (connected == -1 || connected == 1)) {
+       info("%s: Modem doesn't exists or has been removed, device %s is to be closed", name, port);
+       if (fd > 0) {
+           ret = close(fd);
+           if (ret < 0) {
+               error("%s: ERROR: Error when closing device %s after modem removal, ret=%i", name, port, ret);
+           }
+           fd = -2;
+       }
+       connected = 0;
+    }
+
+    /* modem inserted event */
+    if (port_exists > 0 && (connected == -1 || connected == 0)) {
+       info("%s: Modem has been inserted, device %s will be opened", name, port);
+       if (fd < 0) {
+           ret = huawei_configure_port();
+           if (ret > 0) {
+               connected = 1;
+               if (debug)
+                   debug("DEBUG: Device %s configured successfully, fd=%i", port, ret);
+           } else {
+               error("%s: ERROR: Device %s configuring failure->retrying..., ret=%i", name, port, ret);
+           }
+       }
+    }
+
+    /* init variables to zero (except tx/rx total flows) when device disconnected */
+    if (connected == 0) {
+       rssi = 0;
+       sub_mode = 0;
+       last_ds_time = 0;
+       total_ds_time = 0;
+       last_tx_flow = 0;
+       last_rx_flow = 0;
+       calc_tx_rate = 0;
+       calc_rx_rate = 0;
+       strcpy(manuf, "");
+       strcpy(model, "");
+       strcpy(fwver, "");
+       strcpy(operator, "");
+       configured = 0;
+    }
+
+    /* modem initialization */
+    if (connected == 1 && configured != 1) {
+       buf = huawei_send_receive(INIT_STRING);
+       bytes = strlen(buf);
+
+       if (strncmp(buf, "OK", 2) == 0) {
+           configured = 1;
+           info("%s: Modem user inerface succesfully initialized to: \'%s\'", name, INIT_STRING);
+       } else {
+           configured = 0;
+           error("%s: ERROR: Invalid or empty response: \'%s\' received for: \'%s\'", name, buf, INIT_STRING);
+       }
+    }
+
+    return configured;
+}
+
+static void huawei_read_quality(const char *cmd)
+{
+    int bytes;
+    char *buf;
+
+    buf = huawei_send_receive(cmd);
+    bytes = strlen(buf);
+
+    if (strncmp(buf, "+CSQ: ", 6) == 0) {
+
+       /* Returns relative signal strength (RSSI) and ber (not supported by modems).
+        *
+        * +CSQ: 14,99
+        *     rssi,ber
+        *
+        *  0 <= -113 dBm
+        *  1 -111 dBm
+        *  2 to 30 -109 dBm to -53 dBm
+        *  31 >= -51 dBm
+        *  99 unknown or unmeasurable
+        */
+
+       if (scan_uint(buf + 6, 2, &rssi, &ber) != 2)
+           error("%s: ERROR: Cannot parse all +CSQ: data fields, some data may be wrong or missing", name);
+
+       if (debug)
+           debug("DEBUG: Relative rssi value: %u", rssi);
+    } else
+       error("%s: ERROR: Invalid or empty response: \'%s\' received for: \'%s\'", name, buf, cmd);
+
+    return;
+}
+
+static void huawei_read_sysinfo(const char *cmd)
+{
+    int bytes;
+    char *buf;
+
+    buf = huawei_send_receive(cmd);
+    bytes = strlen(buf);
+
+    if (strncmp(buf, "^SYSINFO:", 9) == 0) {
+
+       /* Returns system information.
+        *
+        * ^SYSINFO:2,3,0,5,1,,4
+        *     status,domain,roaming_status,mode,SIM state,reserved,sub_mode
+        */
+
+       if (scan_uint(buf + 9, 7, &status, &domain, &roaming_status, &mode, &sim_state, &reserved, &sub_mode) != 7)
+           error("%s: ERROR: Cannot parse all ^SYSINFO: data fields, some data may be wrong or missing", name);
+
+       if (debug)
+           debug("DEBUG: Sub mode value: %u", sub_mode);
+    } else
+       error("%s: ERROR: Invalid or empty response: \'%s\' received for: \'%s\'", name, buf, cmd);
+
+    return;
+}
+
+static void huawei_read_manuf(const char *cmd)
+{
+    int bytes;
+    char *buf;
+
+    buf = huawei_send_receive(cmd);
+    bytes = strlen(buf);
+
+    /* accept all but "ERROR" */
+    if (bytes > 0 && huawei_recv_error(buf) == 0) {
+
+       /* Returns manufacturer string.
+        * 
+        * huawei
+        */
+       strncpy(manuf, buf, sizeof(manuf) - 1);
+
+       if (debug)
+           debug("DEBUG: Manufacturer string: %s", manuf);
+    } else
+       error("%s: ERROR: Invalid or empty response: \'%s\' received for: \'%s\'", name, buf, cmd);
+
+    return;
+}
+
+static void huawei_read_model(const char *cmd)
+{
+    int bytes;
+    char *buf;
+
+    buf = huawei_send_receive(cmd);
+    bytes = strlen(buf);
+
+    /* accept all but "ERROR" */
+    if (bytes > 0 && huawei_recv_error(buf) == 0) {
+
+       /* Returns model string.
+        * 
+        * E220
+        */
+       strncpy(model, buf, sizeof(model) - 1);
+
+       if (debug)
+           debug("DEBUG: Model string: %s", model);
+    } else
+       error("%s: ERROR: Invalid or empty response: \'%s\' received for: \'%s\'", name, buf, cmd);
+
+    return;
+}
+
+static void huawei_read_fwver(const char *cmd)
+{
+    int bytes;
+    char *buf;
+
+    buf = huawei_send_receive(cmd);
+    bytes = strlen(buf);
+
+    /* accept all but "ERROR" */
+    if (bytes > 0 && huawei_recv_error(buf) == 0) {
+
+       /* Returns firmware version string.
+        * 
+        * 11.110.03.00.00
+        */
+       strncpy(fwver, buf, sizeof(fwver) - 1);
+
+       if (debug)
+           debug("DEBUG: Firmware version string: %s", fwver);
+    } else
+       error("%s: ERROR: Invalid or empty response: \'%s\' received for: \'%s\'", name, buf, cmd);
+
+    return;
+}
+
+static void huawei_read_operator(const char *cmd)
+{
+    int bytes, i, pos, copy;
+    char *buf;
+
+    buf = huawei_send_receive(cmd);
+    bytes = strlen(buf);
+
+    if (strncmp(buf, "+COPS:", 6) == 0) {
+
+       /* Returns operator string.
+        * 
+        * +COPS: 0,0,"vodafone ES",2
+        * reg_mode,format,operator string (based on format),network access type
+        */
+
+       /* parse "operator string" */
+       pos = 0;
+       copy = 0;
+
+       for (i = 6; i <= bytes; i++) {
+           if (buf[i] == '\"' && copy == 0) {
+               i++;
+               copy = 1;
+           } else if ((buf[i] == '\"') && copy == 1)
+               copy = 0;
+
+           if (copy == 1 && strlen(operator) < sizeof(operator) - 1) {
+               operator[pos] = buf[i];
+               pos++;
+           }
+       }
+
+       if (debug)
+           debug("DEBUG: Operator version string: \'%s\'", operator);
+    } else
+       error("%s: ERROR: Invalid or empty response: \'%s\' received for: \'%s\'", name, buf, cmd);
+
+    return;
+}
+
+static void huawei_read_flowreport(const char *cmd)
+{
+    char *buf;
+    int bytes;
+    static unsigned long long int prev_tx_flow = 0, prev_rx_flow = 0;
+    static unsigned long int prev_ds_time = 0;
+
+    buf = huawei_send_receive(cmd);
+    bytes = strlen(buf);
+
+    if (strncmp(buf, "^DSFLOWQRY:", 11) == 0) {
+
+       /* Returns flow report.
+        *
+        * ^DSFLOWQRY:00000E8E,0000000000333ACC,000000000A93E9AD,007B3514,000000007C0E5F69,00000007AD1AE9C6
+        *        last_ds_time,last_rx_flow    ,last_tx_flow    ,total_ds_time,total_tx_flow,total_rx_flow
+        */
+
+       if (sscanf(buf + 11, "%lX,%LX,%LX,%lX,%LX,%LX", &last_ds_time, &last_tx_flow, &last_rx_flow, &total_ds_time,
+                  &total_tx_flow, &total_rx_flow) != 6)
+           error("%s: ERROR: Cannot parse all ^DSFLOWQRY: data fields, some data may be wrong or missing", name);
+
+       /* ^DSFLOWQRY: lacks tx_rate and rx_rate values (^DSFLOWRPT has them), we try to calculate them here. 
+        * Values comes at 2s interval.
+        */
+
+       /* tx_rate calculation */
+       if (last_ds_time >= prev_ds_time + 2 && last_tx_flow > prev_tx_flow)
+           calc_tx_rate = ((double) (last_tx_flow - prev_tx_flow)) / ((double) (last_ds_time - prev_ds_time));
+       else if (last_ds_time >= prev_ds_time + 2 && last_tx_flow == prev_tx_flow)
+           calc_tx_rate = 0;
+
+       /* rx_rate calculation */
+       if (last_ds_time >= prev_ds_time + 2 && last_rx_flow > prev_rx_flow)
+           calc_rx_rate = ((double) (last_rx_flow - prev_rx_flow)) / ((double) (last_ds_time - prev_ds_time));
+       else if (last_ds_time >= prev_ds_time + 2 && last_rx_flow == prev_rx_flow)
+           calc_rx_rate = 0;
+
+       /* previous values */
+       prev_tx_flow = last_tx_flow;
+       prev_rx_flow = last_rx_flow;
+       prev_ds_time = last_ds_time;
+
+       if (debug) {
+           debug("DEBUG: Last DS connection time [s]: %lu", last_ds_time);
+           debug("DEBUG: Last DS transmiting traffic [bytes]: %llu", last_tx_flow);
+           debug("DEBUG: Last DS receiving traffic [bytes]: %llu", last_rx_flow);
+           debug("DEBUG: Total DS connection time [s]: %lu", total_ds_time);
+           debug("DEBUG: Total DS transmiting trafic [bytes]: %llu", total_tx_flow);
+           debug("DEBUG: Total DS receiving trafic [bytes]: %llu", total_rx_flow);
+           debug("DEBUG: Calculated tx rate [bytes/s]: %lf", calc_tx_rate);
+           debug("DEBUG: Calculated rx rate [bytes/s]: %lf", calc_rx_rate);
+       }
+
+    } else
+       error("%s: ERROR: Invalid or empty response: \'%s\' received for: \'%s\'", name, buf, cmd);
+
+    return;
+}
+
+static void my_quality(RESULT * result, RESULT * arg1)
+{
+    int age;
+    struct timeval now;
+    static struct timeval prev_age;
+    static double value;
+
+    gettimeofday(&now, NULL);
+    age = age_diff(prev_age);
+
+    if (age > MIN_INTERVAL && huawei_configured() == 1) {
+       prev_age = now;
+       huawei_read_quality(QUALITY);
+    }
+
+    /* Note: R2S stands for 'Result to String' */
+    if (strncmp(R2S(arg1), "%", 1) == 0) {
+       /* scale rssi 0...31 to 0..100% value */
+       if (rssi > 0 && rssi < 32)
+           value = (double) rssi *100 / 31;
+       else if (rssi == 0)
+           value = 0;
+
+    } else if (strncmp(R2S(arg1), "dbm", 3) == 0) {
+       /* scale rssi 0...31 to -113 dBm...-51 dBm value */
+       if (rssi > 0 && rssi < 32)
+           value = ((double) rssi * 2) - 113;
+       else if (rssi == 0)
+           value = -113;
+
+    } else if (strncmp(R2S(arg1), "rssi", 4) == 0) {
+       /* pass through relative rssi 0...31 value */
+       if (rssi > 0 && rssi < 32)
+           value = (double) rssi;
+       else if (rssi == 0)
+           value = 0;
+
+    } else {
+       error("%s: ERROR: Argument for huawei::quality() is missing, give: '%%'|'dbm'|'rssi'", name);
+       value = 0;
+    }
+
+    /* store result */
+    SetResult(&result, R_NUMBER, &value);
+
+    return;
+}
+
+static void my_mode(RESULT * result, RESULT * arg1)
+{
+    int age;
+    struct timeval now;
+    static struct timeval prev_age;
+    double value_num;
+    char *value_str, *mode_str;
+
+    gettimeofday(&now, NULL);
+    age = age_diff(prev_age);
+
+    if (age > MIN_INTERVAL && huawei_configured() == 1) {
+       prev_age = now;
+       huawei_read_sysinfo(SYSINFO);
+    }
+
+    if (strncmp(R2S(arg1), "text", 4) == 0) {
+       /* sub modes 8 and 9 are unknown */
+       if (sub_mode > 7)
+           sub_mode = 8;
+
+       /* start with an empty string */
+       value_str = strdup("");
+
+       /* mode string */
+       mode_str = sub_system_mode[sub_mode];
+
+       /* allocate memory for value */
+       value_str = realloc(value_str, strlen(mode_str) + 1);
+
+       /* write mode string to value */
+       strcat(value_str, mode_str);
+
+       /* store result */
+       SetResult(&result, R_STRING, value_str);
+
+       /* free local string */
+       free(value_str);
+
+    } else if (strncmp(R2S(arg1), "number", 6) == 0) {
+       value_num = (double) mode;
+
+       /* store result */
+       SetResult(&result, R_NUMBER, &value_num);
+
+    } else {
+       error("%s: ERROR: Argument for huawei::mode() is missing, give: 'text'|'number'", name);
+
+       value_num = 0;
+
+       /* store result */
+       SetResult(&result, R_NUMBER, &value_num);
+    }
+
+    return;
+}
+
+static void my_manuf(RESULT * result)
+{
+    int age;
+    struct timeval now;
+    static struct timeval prev_age;
+    char *value_str;
+
+    gettimeofday(&now, NULL);
+    age = age_diff(prev_age);
+
+    if (age > MIN_INTERVAL && huawei_configured() == 1) {
+       prev_age = now;
+       huawei_read_manuf(MANUF);
+    }
+
+    /* start with an empty string */
+    value_str = strdup("");
+
+    /* allocate memory for value */
+    value_str = realloc(value_str, strlen(manuf) + 1);
+
+    /* write mode string to value */
+    strcat(value_str, manuf);
+
+    /* store result */
+    SetResult(&result, R_STRING, value_str);
+
+    /* free local string */
+    free(value_str);
+
+    return;
+}
+
+static void my_model(RESULT * result)
+{
+    int age;
+    struct timeval now;
+    static struct timeval prev_age;
+    char *value_str;
+
+    gettimeofday(&now, NULL);
+    age = age_diff(prev_age);
+
+    if (age > MIN_INTERVAL && huawei_configured() == 1) {
+       prev_age = now;
+       huawei_read_model(MODEL);
+    }
+
+    /* start with an empty string */
+    value_str = strdup("");
+
+    /* allocate memory for value */
+    value_str = realloc(value_str, strlen(model) + 1);
+
+    /* write mode string to value */
+    strcat(value_str, model);
+
+    /* store result */
+    SetResult(&result, R_STRING, value_str);
+
+    /* free local string */
+    free(value_str);
+
+    return;
+}
+
+static void my_fwver(RESULT * result)
+{
+    int age;
+    struct timeval now;
+    static struct timeval prev_age;
+    char *value_str;
+
+    gettimeofday(&now, NULL);
+    age = age_diff(prev_age);
+
+    if (age > MIN_INTERVAL && huawei_configured() == 1) {
+       prev_age = now;
+       huawei_read_fwver(FWVER);
+    }
+
+    /* start with an empty string */
+    value_str = strdup("");
+
+    /* allocate memory for value */
+    value_str = realloc(value_str, strlen(fwver) + 1);
+
+    /* write mode string to value */
+    strcat(value_str, fwver);
+
+    /* store result */
+    SetResult(&result, R_STRING, value_str);
+
+    /* free local string */
+    free(value_str);
+
+    return;
+}
+
+static void my_operator(RESULT * result)
+{
+    int age;
+    struct timeval now;
+    static struct timeval prev_age;
+    char *value_str;
+
+    gettimeofday(&now, NULL);
+    age = age_diff(prev_age);
+
+    if (age > MIN_INTERVAL && huawei_configured() == 1) {
+       prev_age = now;
+       huawei_read_operator(OPERATOR);
+    }
+
+    /* start with an empty string */
+    value_str = strdup("");
+
+    /* allocate memory for value */
+    value_str = realloc(value_str, strlen(operator) + 1);
+
+    /* write mode string to value */
+    strcat(value_str, operator);
+
+    /* store result */
+    SetResult(&result, R_STRING, value_str);
+
+    /* free local string */
+    free(value_str);
+
+    return;
+}
+
+static void my_flowreport(RESULT * result, RESULT * arg1)
+{
+    int age;
+    unsigned int days, hours, mins, secs;
+    double value_num;
+    char value_str[32];
+    struct timeval now;
+    static struct timeval prev_age;
+
+    gettimeofday(&now, NULL);
+    age = age_diff(prev_age);
+
+    if (age > MIN_INTERVAL && huawei_configured() == 1) {
+       prev_age = now;
+       huawei_read_flowreport(FLOWREPORT);
+    }
+
+    if (strncmp(R2S(arg1), "uptime", 6) == 0) {
+
+       days = last_ds_time / 86400;
+       hours = (last_ds_time / 3600) - (days * 24);
+       mins = (last_ds_time / 60) - (days * 1440) - (hours * 60);
+       secs = last_ds_time % 60;
+
+       if (days > 0)
+           sprintf(value_str, "%u days %02u:%02u:%02u", days, hours, mins, secs);
+       else
+           sprintf(value_str, "%02u:%02u:%02u", hours, mins, secs);
+
+       /* store result: days, hours, mins, secs */
+       SetResult(&result, R_STRING, value_str);
+
+       return;
+    }
+
+    if (strncmp(R2S(arg1), "uptime_seconds", 14) == 0) {
+       /* uptime in seconds */
+       value_num = (double) last_ds_time;
+
+       /* store result: seconds */
+       SetResult(&result, R_NUMBER, &value_num);
+
+       return;
+    }
+
+    if (strncmp(R2S(arg1), "tx_rate", 7) == 0) {
+       /* tx data in Bytes/s */
+       value_num = calc_tx_rate;
+
+       /* store result */
+       SetResult(&result, R_NUMBER, &value_num);
+
+       return;
+    }
+
+    if (strncmp(R2S(arg1), "rx_rate", 7) == 0) {
+       /* rx data in Bytes/s */
+       value_num = calc_rx_rate;
+
+       /* store result */
+       SetResult(&result, R_NUMBER, &value_num);
+
+       return;
+    }
+
+    if (strncmp(R2S(arg1), "total_tx", 8) == 0) {
+       /* total rx data in Bytes */
+       value_num = (double) total_tx_flow;
+
+       /* store result */
+       SetResult(&result, R_NUMBER, &value_num);
+
+       return;
+    }
+
+    if (strncmp(R2S(arg1), "total_rx", 8) == 0) {
+       /* total rx data in Bytes */
+       value_num = (double) total_rx_flow;
+
+       /* store result */
+       SetResult(&result, R_NUMBER, &value_num);
+
+       return;
+    }
+
+    error
+       ("%s: ERROR: Argument for huawei::flowreport() is missing, give: 'uptime'|'uptime_seconds'|'tx_rate'|'rx_rate'|'total_tx'|'total_rx')",
+        name);
+    value_num = 0;
+
+    /* store result */
+    SetResult(&result, R_NUMBER, &value_num);
+
+    return;
+}
+
+/* plugin initialization. MUST NOT be declared 'static'! */
+int plugin_init_huawei(void)
+{
+    /* register our functions */
+    AddFunction("huawei::quality", 1, my_quality);
+    AddFunction("huawei::mode", 1, my_mode);
+    AddFunction("huawei::model", 0, my_model);
+    AddFunction("huawei::manuf", 0, my_manuf);
+    AddFunction("huawei::fwver", 0, my_fwver);
+    AddFunction("huawei::operator", 0, my_operator);
+    AddFunction("huawei::flowreport", 1, my_flowreport);
+
+    return 0;
+}
+
+void plugin_exit_huawei(void)
+{
+    int ret;
+
+    /* close file descriptor */
+    if (fd > 0) {
+       ret = close(fd);
+       if (ret < 0)
+           error("%s: ERROR: Device %s closing failure on plugin_exit, %i", name, port, ret);
+    }
+    fd = -2;
+}