/* $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.
 *
 */


/* 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 */
    "GPRS",			/* 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", (int) 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;
    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);

	if (strncmp(buf, "OK", 2) == 0) {
	    configured = 1;
	    info("%s: Modem user inerface successfully 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)
{
    char *buf;

    buf = huawei_send_receive(cmd);

    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)
{
    char *buf;

    buf = huawei_send_receive(cmd);

    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 = 0, copy = 0;
    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" */
	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;
    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);

    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;
    static struct timeval prev_age;
    static double value;

    age = age_diff(prev_age);

    if (age < 0 || age >= MIN_INTERVAL) {
	gettimeofday(&prev_age, NULL);

	if (huawei_configured() == 1)
	    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;
    static struct timeval prev_age;
    double value_num;
    char *value_str, *mode_str;

    age = age_diff(prev_age);

    if (age < 0 || age >= MIN_INTERVAL) {
	gettimeofday(&prev_age, NULL);

	if (huawei_configured() == 1)
	    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;
    static struct timeval prev_age;
    char *value_str;

    age = age_diff(prev_age);

    if (age < 0 || age >= MIN_INTERVAL) {
	gettimeofday(&prev_age, NULL);

	if (huawei_configured() == 1)
	    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;
    static struct timeval prev_age;
    char *value_str;

    age = age_diff(prev_age);

    if (age < 0 || age >= MIN_INTERVAL) {
	gettimeofday(&prev_age, NULL);

	if (huawei_configured() == 1)
	    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;
    static struct timeval prev_age;
    char *value_str;

    age = age_diff(prev_age);

    if (age < 0 || age >= MIN_INTERVAL) {
	gettimeofday(&prev_age, NULL);

	if (huawei_configured() == 1)
	    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;
    static struct timeval prev_age;
    char *value_str;

    age = age_diff(prev_age);

    if (age < 0 || age >= MIN_INTERVAL) {
	gettimeofday(&prev_age, NULL);

	if (huawei_configured() == 1)
	    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];
    static struct timeval prev_age;

    age = age_diff(prev_age);

    if (age < 0 || age >= MIN_INTERVAL) {
	gettimeofday(&prev_age, NULL);

	if (huawei_configured() == 1)
	    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;
}
