/* $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 * * 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 for contributing the scan_uint() function and some other code. * * Based on sample plugin which is * Copyright (C) 2003 Michael Reinelt * Copyright (C) 2004, 2005, 2006, 2007, 2008 The LCD4Linux Team * * * 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 /* standard buffered input/output */ #include /* standard library definitions */ #include /* for va_start et al */ #include /* string operations */ #include /* read(), close() */ #include /* open() */ #include /* tcflush() */ #include /* data types */ #include /* stat structure */ #include /* timeval structure */ #include /* system error numbers */ /* these should always be included */ #include "debug.h" #include "plugin.h" #ifdef WITH_DMALLOC #include #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
# Sweden - Gusum
# T freq bw fec_hi fec_lo mod transmission-mode guard-interval hierarchy
[CHANNEL]
	DELIVERY_SYSTEM = DVBT
	FREQUENCY = 482000000
	BANDWIDTH_HZ = 8000000
	CODE_RATE_HP = 2/3
	CODE_RATE_LP = NONE
	MODULATION = QAM/64
	TRANSMISSION_MODE = 8K
	GUARD_INTERVAL = 1/8
	HIERARCHY = NONE
	INVERSION = AUTO
rn 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 ( 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 becaus ("%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; }