1575 lines
41 KiB
C
1575 lines
41 KiB
C
/* usbhid-ups.c - Driver for USB and serial (MGE SHUT) HID UPS units
|
|
*
|
|
* Copyright (C)
|
|
* 2003-2012 Arnaud Quette <arnaud.quette@gmail.com>
|
|
* 2005 John Stamp <kinsayder@hotmail.com>
|
|
* 2005-2006 Peter Selinger <selinger@users.sourceforge.net>
|
|
* 2007-2009 Arjen de Korte <adkorte-guest@alioth.debian.org>
|
|
*
|
|
* This program was sponsored by MGE UPS SYSTEMS, and now Eaton
|
|
*
|
|
* 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
|
|
*
|
|
* TODO list:
|
|
* - set ST_FLAG_RW according to HIDData_t->Attribute (ATTR_DATA_CST-ATTR_NVOL_VOL)
|
|
*/
|
|
|
|
#define DRIVER_NAME "Generic HID driver"
|
|
#define DRIVER_VERSION "0.39"
|
|
|
|
#include "main.h"
|
|
#include "libhid.h"
|
|
#include "usbhid-ups.h"
|
|
#include "hidparser.h"
|
|
#include "hidtypes.h"
|
|
|
|
/* include all known subdrivers */
|
|
#include "mge-hid.h"
|
|
|
|
#ifndef SHUT_MODE
|
|
#include "explore-hid.h"
|
|
#include "apc-hid.h"
|
|
#include "belkin-hid.h"
|
|
#include "cps-hid.h"
|
|
#include "liebert-hid.h"
|
|
#include "powercom-hid.h"
|
|
#include "tripplite-hid.h"
|
|
#include "idowell-hid.h"
|
|
#include "openups-hid.h"
|
|
#endif
|
|
|
|
/* master list of avaiable subdrivers */
|
|
static subdriver_t *subdriver_list[] = {
|
|
#ifndef SHUT_MODE
|
|
&explore_subdriver,
|
|
#endif
|
|
&mge_subdriver,
|
|
#ifndef SHUT_MODE
|
|
&apc_subdriver,
|
|
&belkin_subdriver,
|
|
&cps_subdriver,
|
|
&liebert_subdriver,
|
|
&powercom_subdriver,
|
|
&tripplite_subdriver,
|
|
&idowell_subdriver,
|
|
&openups_subdriver,
|
|
#endif
|
|
NULL
|
|
};
|
|
|
|
upsdrv_info_t upsdrv_info = {
|
|
DRIVER_NAME,
|
|
DRIVER_VERSION,
|
|
"Arnaud Quette <arnaud.quette@gmail.com>\n" \
|
|
"Peter Selinger <selinger@users.sourceforge.net>\n" \
|
|
"Arjen de Korte <adkorte-guest@alioth.debian.org>\n" \
|
|
"John Stamp <kinsayder@hotmail.com>",
|
|
/*FIXME: link the subdrivers? do the same as for the mibs! */
|
|
#ifndef SHUT_MODE
|
|
DRV_STABLE,
|
|
#else
|
|
DRV_EXPERIMENTAL,
|
|
#endif
|
|
{ &comm_upsdrv_info, NULL }
|
|
};
|
|
|
|
/* Data walk modes */
|
|
typedef enum {
|
|
HU_WALKMODE_INIT = 0,
|
|
HU_WALKMODE_QUICK_UPDATE,
|
|
HU_WALKMODE_FULL_UPDATE
|
|
} walkmode_t;
|
|
|
|
/* pointer to the active subdriver object (changed in callback() function) */
|
|
static subdriver_t *subdriver = NULL;
|
|
|
|
/* Global vars */
|
|
static HIDDevice_t *hd = NULL;
|
|
static HIDDevice_t curDevice = { 0x0000, 0x0000, NULL, NULL, NULL, NULL };
|
|
static HIDDeviceMatcher_t *subdriver_matcher = NULL;
|
|
#ifndef SHUT_MODE
|
|
static HIDDeviceMatcher_t *exact_matcher = NULL;
|
|
static HIDDeviceMatcher_t *regex_matcher = NULL;
|
|
#endif
|
|
static int pollfreq = DEFAULT_POLLFREQ;
|
|
static int ups_status = 0;
|
|
static bool_t data_has_changed = FALSE; /* for SEMI_STATIC data polling */
|
|
#ifndef SUN_LIBUSB
|
|
bool_t use_interrupt_pipe = TRUE;
|
|
#else
|
|
bool_t use_interrupt_pipe = FALSE;
|
|
#endif
|
|
static time_t lastpoll; /* Timestamp the last polling */
|
|
hid_dev_handle_t udev;
|
|
|
|
/* support functions */
|
|
static hid_info_t *find_nut_info(const char *varname);
|
|
static hid_info_t *find_hid_info(const HIDData_t *hiddata);
|
|
static const char *hu_find_infoval(info_lkp_t *hid2info, const double value);
|
|
static long hu_find_valinfo(info_lkp_t *hid2info, const char* value);
|
|
static void process_boolean_info(const char *nutvalue);
|
|
static void ups_alarm_set(void);
|
|
static void ups_status_set(void);
|
|
static bool_t hid_ups_walk(walkmode_t mode);
|
|
static int reconnect_ups(void);
|
|
static int ups_infoval_set(hid_info_t *item, double value);
|
|
static int callback(hid_dev_handle_t udev, HIDDevice_t *hd, unsigned char *rdbuf, int rdlen);
|
|
#ifdef DEBUG
|
|
static double interval(void);
|
|
#endif
|
|
|
|
/* global variables */
|
|
HIDDesc_t *pDesc = NULL; /* parsed Report Descriptor */
|
|
reportbuf_t *reportbuf = NULL; /* buffer for most recent reports */
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* data for processing boolean values from UPS */
|
|
|
|
#define STATUS(x) ((unsigned)1<<x)
|
|
|
|
typedef enum {
|
|
ONLINE = 0, /* on line */
|
|
DISCHRG, /* discharging */
|
|
CHRG, /* charging */
|
|
LOWBATT, /* low battery */
|
|
OVERLOAD, /* overload */
|
|
REPLACEBATT, /* replace battery */
|
|
SHUTDOWNIMM, /* shutdown imminent */
|
|
TRIM, /* SmartTrim */
|
|
BOOST, /* SmartBoost */
|
|
BYPASSAUTO, /* on automatic bypass */
|
|
BYPASSMAN, /* on manual/service bypass */
|
|
OFF, /* ups is off */
|
|
CAL, /* calibration */
|
|
OVERHEAT, /* overheat; Belkin, TrippLite */
|
|
COMMFAULT, /* UPS fault; Belkin, TrippLite */
|
|
DEPLETED, /* battery depleted; Belkin */
|
|
TIMELIMITEXP, /* time limit expired; APC */
|
|
FULLYCHARGED, /* battery full; CyberPower */
|
|
AWAITINGPOWER, /* awaiting power; Belkin, TrippLite */
|
|
FANFAIL, /* fan failure; MGE */
|
|
NOBATTERY, /* battery missing; MGE */
|
|
BATTVOLTLO, /* battery voltage too low; MGE */
|
|
BATTVOLTHI, /* battery voltage too high; MGE */
|
|
CHARGERFAIL, /* battery charger failure; MGE */
|
|
VRANGE, /* voltage out of range */
|
|
FRANGE /* frequency out of range */
|
|
} status_bit_t;
|
|
|
|
|
|
/* --------------------------------------------------------------- */
|
|
/* Struct & data for boolean processing */
|
|
/* --------------------------------------------------------------- */
|
|
|
|
/* Note: this structure holds internal status info, directly as
|
|
collected from the hardware; not yet converted to official NUT
|
|
status or alarms */
|
|
typedef struct {
|
|
const char *status_str; /* ups status string */
|
|
const int status_mask; /* ups status mask */
|
|
} status_lkp_t;
|
|
|
|
static status_lkp_t status_info[] = {
|
|
/* map internal status strings to bit masks */
|
|
{ "online", STATUS(ONLINE) },
|
|
{ "dischrg", STATUS(DISCHRG) },
|
|
{ "chrg", STATUS(CHRG) },
|
|
{ "lowbatt", STATUS(LOWBATT) },
|
|
{ "overload", STATUS(OVERLOAD) },
|
|
{ "replacebatt", STATUS(REPLACEBATT) },
|
|
{ "shutdownimm", STATUS(SHUTDOWNIMM) },
|
|
{ "trim", STATUS(TRIM) },
|
|
{ "boost", STATUS(BOOST) },
|
|
{ "bypassauto", STATUS(BYPASSAUTO) },
|
|
{ "bypassman", STATUS(BYPASSMAN) },
|
|
{ "off", STATUS(OFF) },
|
|
{ "cal", STATUS(CAL) },
|
|
{ "overheat", STATUS(OVERHEAT) },
|
|
{ "commfault", STATUS(COMMFAULT) },
|
|
{ "depleted", STATUS(DEPLETED) },
|
|
{ "timelimitexp", STATUS(TIMELIMITEXP) },
|
|
{ "fullycharged", STATUS(FULLYCHARGED) },
|
|
{ "awaitingpower", STATUS(AWAITINGPOWER) },
|
|
{ "fanfail", STATUS(FANFAIL) },
|
|
{ "nobattery", STATUS(NOBATTERY) },
|
|
{ "battvoltlo", STATUS(BATTVOLTLO) },
|
|
{ "battvolthi", STATUS(BATTVOLTHI) },
|
|
{ "chargerfail", STATUS(CHARGERFAIL) },
|
|
{ "vrange", STATUS(VRANGE) },
|
|
{ "frange", STATUS(FRANGE) },
|
|
{ NULL, 0 },
|
|
};
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* value lookup tables and generic lookup functions */
|
|
|
|
/* Actual value lookup tables => should be fine for all Mfrs (TODO: validate it!) */
|
|
|
|
/* the purpose of the following status conversions is to collect
|
|
information, not to interpret it. The function
|
|
process_boolean_info() remembers these values by updating the global
|
|
variable ups_status. Interpretation happens in ups_status_set,
|
|
where they are converted to standard NUT status strings. Notice
|
|
that the below conversions do not yield standard NUT status
|
|
strings; this in indicated being in lower-case characters.
|
|
|
|
The reason to separate the collection of information from its
|
|
interpretation is that not each report received from the UPS may
|
|
contain all the status flags, so they must be stored
|
|
somewhere. Also, there can be more than one status flag triggering
|
|
a certain condition (e.g. a certain UPS might have variables
|
|
low_battery, shutdown_imminent, timelimit_exceeded, and each of
|
|
these would trigger the NUT status LB. But we have to ensure that
|
|
these variables don't unset each other, so they are remembered
|
|
separately) */
|
|
|
|
info_lkp_t online_info[] = {
|
|
{ 1, "online", NULL },
|
|
{ 0, "!online", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
info_lkp_t discharging_info[] = {
|
|
{ 1, "dischrg", NULL },
|
|
{ 0, "!dischrg", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
info_lkp_t charging_info[] = {
|
|
{ 1, "chrg", NULL },
|
|
{ 0, "!chrg", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
info_lkp_t lowbatt_info[] = {
|
|
{ 1, "lowbatt", NULL },
|
|
{ 0, "!lowbatt", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
info_lkp_t overload_info[] = {
|
|
{ 1, "overload", NULL },
|
|
{ 0, "!overload", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
info_lkp_t replacebatt_info[] = {
|
|
{ 1, "replacebatt", NULL },
|
|
{ 0, "!replacebatt", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
info_lkp_t trim_info[] = {
|
|
{ 1, "trim", NULL },
|
|
{ 0, "!trim", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
info_lkp_t boost_info[] = {
|
|
{ 1, "boost", NULL },
|
|
{ 0, "!boost", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
info_lkp_t bypass_auto_info[] = {
|
|
{ 1, "bypassauto", NULL },
|
|
{ 0, "!bypassauto", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
info_lkp_t bypass_manual_info[] = {
|
|
{ 1, "bypassman", NULL },
|
|
{ 0, "!bypassman", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
/* note: this value is reverted (0=set, 1=not set). We report "being
|
|
off" rather than "being on", so that devices that don't implement
|
|
this variable are "on" by default */
|
|
info_lkp_t off_info[] = {
|
|
{ 0, "off", NULL },
|
|
{ 1, "!off", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
info_lkp_t calibration_info[] = {
|
|
{ 1, "cal", NULL },
|
|
{ 0, "!cal", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
/* note: this value is reverted (0=set, 1=not set). We report "battery
|
|
not installed" rather than "battery installed", so that devices
|
|
that don't implement this variable have a battery by default */
|
|
info_lkp_t nobattery_info[] = {
|
|
{ 1, "!nobattery", NULL },
|
|
{ 0, "nobattery", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
info_lkp_t fanfail_info[] = {
|
|
{ 1, "fanfail", NULL },
|
|
{ 0, "!fanfail", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
info_lkp_t shutdownimm_info[] = {
|
|
{ 1, "shutdownimm", NULL },
|
|
{ 0, "!shutdownimm", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
info_lkp_t overheat_info[] = {
|
|
{ 1, "overheat", NULL },
|
|
{ 0, "!overheat", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
info_lkp_t awaitingpower_info[] = {
|
|
{ 1, "awaitingpower", NULL },
|
|
{ 0, "!awaitingpower", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
info_lkp_t commfault_info[] = {
|
|
{ 1, "commfault", NULL },
|
|
{ 0, "!commfault", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
info_lkp_t timelimitexpired_info[] = {
|
|
{ 1, "timelimitexp", NULL },
|
|
{ 0, "!timelimitexp", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
info_lkp_t battvoltlo_info[] = {
|
|
{ 1, "battvoltlo", NULL },
|
|
{ 0, "!battvoltlo", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
info_lkp_t battvolthi_info[] = {
|
|
{ 1, "battvolthi", NULL },
|
|
{ 0, "!battvolthi", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
info_lkp_t chargerfail_info[] = {
|
|
{ 1, "chargerfail", NULL },
|
|
{ 0, "!chargerfail", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
info_lkp_t fullycharged_info[] = { /* used by CyberPower and TrippLite */
|
|
{ 1, "fullycharged", NULL },
|
|
{ 0, "!fullycharged", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
info_lkp_t depleted_info[] = {
|
|
{ 1, "depleted", NULL },
|
|
{ 0, "!depleted", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
info_lkp_t vrange_info[] = {
|
|
{ 0, "!vrange", NULL },
|
|
{ 1, "vrange", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
info_lkp_t frange_info[] = {
|
|
{ 0, "!frange", NULL },
|
|
{ 1, "frange", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
|
|
info_lkp_t test_write_info[] = {
|
|
{ 0, "No test", NULL },
|
|
{ 1, "Quick test", NULL },
|
|
{ 2, "Deep test", NULL },
|
|
{ 3, "Abort test", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
|
|
info_lkp_t test_read_info[] = {
|
|
{ 1, "Done and passed", NULL },
|
|
{ 2, "Done and warning", NULL },
|
|
{ 3, "Done and error", NULL },
|
|
{ 4, "Aborted", NULL },
|
|
{ 5, "In progress", NULL },
|
|
{ 6, "No test initiated", NULL },
|
|
{ 7, "Test scheduled", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
|
|
info_lkp_t beeper_info[] = {
|
|
{ 1, "disabled", NULL },
|
|
{ 2, "enabled", NULL },
|
|
{ 3, "muted", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
|
|
info_lkp_t yes_no_info[] = {
|
|
{ 0, "no", NULL },
|
|
{ 1, "yes", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
|
|
info_lkp_t on_off_info[] = {
|
|
{ 0, "off", NULL },
|
|
{ 1, "on", NULL },
|
|
{ 0, NULL, NULL }
|
|
};
|
|
|
|
/* returns statically allocated string - must not use it again before
|
|
done with result! */
|
|
static const char *date_conversion_fun(double value)
|
|
{
|
|
static char buf[20];
|
|
int year, month, day;
|
|
|
|
if ((long)value == 0) {
|
|
return "not set";
|
|
}
|
|
|
|
year = 1980 + ((long)value >> 9); /* negative value represents pre-1980 date */
|
|
month = ((long)value >> 5) & 0x0f;
|
|
day = (long)value & 0x1f;
|
|
|
|
snprintf(buf, sizeof(buf), "%04d/%02d/%02d", year, month, day);
|
|
|
|
return buf;
|
|
}
|
|
|
|
info_lkp_t date_conversion[] = {
|
|
{ 0, NULL, date_conversion_fun }
|
|
};
|
|
|
|
/* returns statically allocated string - must not use it again before
|
|
done with result! */
|
|
static const char *hex_conversion_fun(double value)
|
|
{
|
|
static char buf[20];
|
|
|
|
snprintf(buf, sizeof(buf), "%08lx", (long)value);
|
|
|
|
return buf;
|
|
}
|
|
|
|
info_lkp_t hex_conversion[] = {
|
|
{ 0, NULL, hex_conversion_fun }
|
|
};
|
|
|
|
/* returns statically allocated string - must not use it again before
|
|
done with result! */
|
|
static const char *stringid_conversion_fun(double value)
|
|
{
|
|
static char buf[20];
|
|
|
|
return HIDGetIndexString(udev, (int)value, buf, sizeof(buf));
|
|
}
|
|
|
|
info_lkp_t stringid_conversion[] = {
|
|
{ 0, NULL, stringid_conversion_fun }
|
|
};
|
|
|
|
/* returns statically allocated string - must not use it again before
|
|
done with result! */
|
|
static const char *divide_by_10_conversion_fun(double value)
|
|
{
|
|
static char buf[20];
|
|
|
|
snprintf(buf, sizeof(buf), "%0.1f", value * 0.1);
|
|
|
|
return buf;
|
|
}
|
|
|
|
info_lkp_t divide_by_10_conversion[] = {
|
|
{ 0, NULL, divide_by_10_conversion_fun }
|
|
};
|
|
|
|
/* returns statically allocated string - must not use it again before
|
|
done with result! */
|
|
static const char *kelvin_celsius_conversion_fun(double value)
|
|
{
|
|
static char buf[20];
|
|
|
|
/* check if the value is in the Kelvin range, to
|
|
* detect buggy value (already expressed in °C), as found
|
|
* on some HP implementation */
|
|
if ((value >= 273) && (value <= 373)) {
|
|
/* the value is indeed in °K */
|
|
snprintf(buf, sizeof(buf), "%.1f", value - 273.15);
|
|
}
|
|
else {
|
|
/* else, this is actually °C, not °K! */
|
|
snprintf(buf, sizeof(buf), "%.1f", value);
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
info_lkp_t kelvin_celsius_conversion[] = {
|
|
{ 0, NULL, kelvin_celsius_conversion_fun }
|
|
};
|
|
|
|
/*!
|
|
* subdriver matcher: only useful for USB mode
|
|
* as SHUT is only supported by MGE UPS SYSTEMS units
|
|
*/
|
|
|
|
#ifndef SHUT_MODE
|
|
static int match_function_subdriver(HIDDevice_t *d, void *privdata) {
|
|
int i;
|
|
|
|
for (i=0; subdriver_list[i] != NULL; i++) {
|
|
if (subdriver_list[i]->claim(d)) {
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static HIDDeviceMatcher_t subdriver_matcher_struct = {
|
|
match_function_subdriver,
|
|
NULL,
|
|
NULL
|
|
};
|
|
#endif
|
|
|
|
/* ---------------------------------------------
|
|
* driver functions implementations
|
|
* --------------------------------------------- */
|
|
/* process instant command and take action. */
|
|
int instcmd(const char *cmdname, const char *extradata)
|
|
{
|
|
hid_info_t *hidups_item;
|
|
const char *val;
|
|
double value;
|
|
|
|
if (!strcasecmp(cmdname, "beeper.off")) {
|
|
/* compatibility mode for old command */
|
|
upslogx(LOG_WARNING,
|
|
"The 'beeper.off' command has been renamed to 'beeper.disable'");
|
|
return instcmd("beeper.disable", NULL);
|
|
}
|
|
|
|
if (!strcasecmp(cmdname, "beeper.on")) {
|
|
/* compatibility mode for old command */
|
|
upslogx(LOG_WARNING,
|
|
"The 'beeper.on' command has been renamed to 'beeper.enable'");
|
|
return instcmd("beeper.enable", NULL);
|
|
}
|
|
|
|
upsdebugx(1, "instcmd(%s, %s)", cmdname, extradata ? extradata : "[NULL]");
|
|
|
|
/* Retrieve and check netvar & item_path */
|
|
hidups_item = find_nut_info(cmdname);
|
|
|
|
/* Check for fallback if not found */
|
|
if (hidups_item == NULL) {
|
|
|
|
if (!strcasecmp(cmdname, "load.on")) {
|
|
return instcmd("load.on.delay", "0");
|
|
}
|
|
|
|
if (!strcasecmp(cmdname, "load.off")) {
|
|
return instcmd("load.off.delay", "0");
|
|
}
|
|
|
|
if (!strcasecmp(cmdname, "shutdown.return")) {
|
|
int ret;
|
|
|
|
/* Ensure "ups.start.auto" is set to "yes", if supported */
|
|
if (dstate_getinfo("ups.start.auto")) {
|
|
setvar("ups.start.auto", "yes");
|
|
}
|
|
|
|
ret = instcmd("load.on.delay", dstate_getinfo("ups.delay.start"));
|
|
if (ret != STAT_INSTCMD_HANDLED) {
|
|
return ret;
|
|
}
|
|
|
|
return instcmd("load.off.delay", dstate_getinfo("ups.delay.shutdown"));
|
|
}
|
|
|
|
if (!strcasecmp(cmdname, "shutdown.stayoff")) {
|
|
int ret;
|
|
|
|
/* Ensure "ups.start.auto" is set to "no", if supported */
|
|
if (dstate_getinfo("ups.start.auto")) {
|
|
setvar("ups.start.auto", "no");
|
|
}
|
|
|
|
ret = instcmd("load.on.delay", "-1");
|
|
if (ret != STAT_INSTCMD_HANDLED) {
|
|
return ret;
|
|
}
|
|
|
|
return instcmd("load.off.delay", dstate_getinfo("ups.delay.shutdown"));
|
|
}
|
|
|
|
upsdebugx(2, "instcmd: info element unavailable %s\n", cmdname);
|
|
return STAT_INSTCMD_INVALID;
|
|
}
|
|
|
|
/* Check if the item is an instant command */
|
|
if (!(hidups_item->hidflags & HU_TYPE_CMD)) {
|
|
upsdebugx(2, "instcmd: %s is not an instant command\n", cmdname);
|
|
return STAT_INSTCMD_INVALID;
|
|
}
|
|
|
|
/* If extradata is empty, use the default value from the HID-to-NUT table */
|
|
val = extradata ? extradata : hidups_item->dfl;
|
|
|
|
/* Lookup the new value if needed */
|
|
if (hidups_item->hid2info != NULL) {
|
|
value = hu_find_valinfo(hidups_item->hid2info, val);
|
|
} else {
|
|
value = atol(val);
|
|
}
|
|
|
|
/* Actual variable setting */
|
|
if (HIDSetDataValue(udev, hidups_item->hiddata, value) == 1) {
|
|
upsdebugx(5, "instcmd: SUCCEED\n");
|
|
/* Set the status so that SEMI_STATIC vars are polled */
|
|
data_has_changed = TRUE;
|
|
return STAT_INSTCMD_HANDLED;
|
|
}
|
|
|
|
upsdebugx(3, "instcmd: FAILED\n"); /* TODO: HANDLED but FAILED, not UNKNOWN! */
|
|
return STAT_INSTCMD_FAILED;
|
|
}
|
|
|
|
/* set r/w variable to a value. */
|
|
int setvar(const char *varname, const char *val)
|
|
{
|
|
hid_info_t *hidups_item;
|
|
double value;
|
|
|
|
upsdebugx(1, "setvar(%s, %s)", varname, val);
|
|
|
|
/* retrieve and check netvar & item_path */
|
|
hidups_item = find_nut_info(varname);
|
|
|
|
if (hidups_item == NULL) {
|
|
upsdebugx(2, "setvar: info element unavailable %s\n", varname);
|
|
return STAT_SET_UNKNOWN;
|
|
}
|
|
|
|
/* Checking item writability and HID Path */
|
|
if (!(hidups_item->info_flags & ST_FLAG_RW)) {
|
|
upsdebugx(2, "setvar: not writable %s\n", varname);
|
|
return STAT_SET_UNKNOWN;
|
|
}
|
|
|
|
/* handle server side variable */
|
|
if (hidups_item->hidflags & HU_FLAG_ABSENT) {
|
|
upsdebugx(2, "setvar: setting server side variable %s\n", varname);
|
|
dstate_setinfo(hidups_item->info_type, "%s", val);
|
|
return STAT_SET_HANDLED;
|
|
}
|
|
|
|
/* HU_FLAG_ABSENT is the only case of HID Path == NULL */
|
|
if (hidups_item->hidpath == NULL) {
|
|
upsdebugx(2, "setvar: ID Path is NULL for %s\n", varname);
|
|
return STAT_SET_UNKNOWN;
|
|
}
|
|
|
|
/* Lookup the new value if needed */
|
|
if (hidups_item->hid2info != NULL) {
|
|
value = hu_find_valinfo(hidups_item->hid2info, val);
|
|
} else {
|
|
value = atol(val);
|
|
}
|
|
|
|
/* Actual variable setting */
|
|
if (HIDSetDataValue(udev, hidups_item->hiddata, value) == 1) {
|
|
upsdebugx(5, "setvar: SUCCEED\n");
|
|
/* Set the status so that SEMI_STATIC vars are polled */
|
|
data_has_changed = TRUE;
|
|
return STAT_SET_HANDLED;
|
|
}
|
|
|
|
upsdebugx(3, "setvar: FAILED\n"); /* FIXME: HANDLED but FAILED, not UNKNOWN! */
|
|
return STAT_SET_UNKNOWN;
|
|
}
|
|
|
|
void upsdrv_shutdown(void)
|
|
{
|
|
upsdebugx(1, "upsdrv_shutdown...");
|
|
|
|
/* Try to shutdown with delay */
|
|
if (instcmd("shutdown.return", NULL) == STAT_INSTCMD_HANDLED) {
|
|
/* Shutdown successful */
|
|
return;
|
|
}
|
|
|
|
/* If the above doesn't work, try shutdown.reboot */
|
|
if (instcmd("shutdown.reboot", NULL) == STAT_INSTCMD_HANDLED) {
|
|
/* Shutdown successful */
|
|
return;
|
|
}
|
|
|
|
/* If the above doesn't work, try load.off.delay */
|
|
if (instcmd("load.off.delay", NULL) == STAT_INSTCMD_HANDLED) {
|
|
/* Shutdown successful */
|
|
return;
|
|
}
|
|
|
|
fatalx(EXIT_FAILURE, "Shutdown failed!");
|
|
}
|
|
|
|
void upsdrv_help(void)
|
|
{
|
|
/* FIXME: to be completed */
|
|
}
|
|
|
|
void upsdrv_makevartable(void)
|
|
{
|
|
char temp [MAX_STRING_SIZE];
|
|
|
|
upsdebugx(1, "upsdrv_makevartable...");
|
|
|
|
snprintf(temp, sizeof(temp), "Set low battery level, in %% (default=%s).", DEFAULT_LOWBATT);
|
|
addvar (VAR_VALUE, HU_VAR_LOWBATT, temp);
|
|
|
|
snprintf(temp, sizeof(temp), "Set shutdown delay, in seconds (default=%s)", DEFAULT_OFFDELAY);
|
|
addvar(VAR_VALUE, HU_VAR_OFFDELAY, temp);
|
|
|
|
snprintf(temp, sizeof(temp), "Set startup delay, in seconds (default=%s)", DEFAULT_ONDELAY);
|
|
addvar(VAR_VALUE, HU_VAR_ONDELAY, temp);
|
|
|
|
snprintf(temp, sizeof(temp), "Set polling frequency, in seconds, to reduce data flow (default=%d)",
|
|
DEFAULT_POLLFREQ);
|
|
addvar(VAR_VALUE, HU_VAR_POLLFREQ, temp);
|
|
|
|
addvar(VAR_FLAG, "pollonly", "Don't use interrupt pipe, only use polling");
|
|
|
|
#ifndef SHUT_MODE
|
|
/* allow -x vendor=X, vendorid=X, product=X, productid=X, serial=X */
|
|
nut_usb_addvars();
|
|
|
|
addvar(VAR_FLAG, "explore", "Diagnostic matching of unsupported UPS");
|
|
addvar(VAR_FLAG, "maxreport", "Activate tweak for buggy APC Back-UPS firmware");
|
|
addvar(VAR_FLAG, "interruptonly", "Don't use polling, only use interrupt pipe");
|
|
addvar(VAR_VALUE, "interruptsize", "Number of bytes to read from interrupt pipe");
|
|
#else
|
|
addvar(VAR_VALUE, "notification", "Set notification type, (ignored, only for backward compatibility)");
|
|
#endif
|
|
}
|
|
|
|
#define MAX_EVENT_NUM 32
|
|
|
|
void upsdrv_updateinfo(void)
|
|
{
|
|
hid_info_t *item;
|
|
HIDData_t *event[MAX_EVENT_NUM];
|
|
int i, evtCount;
|
|
double value;
|
|
time_t now;
|
|
|
|
upsdebugx(1, "upsdrv_updateinfo...");
|
|
|
|
time(&now);
|
|
|
|
/* check for device availability to set datastale! */
|
|
if (hd == NULL) {
|
|
/* don't flood reconnection attempts */
|
|
if (now < (int)(lastpoll + poll_interval)) {
|
|
return;
|
|
}
|
|
|
|
upsdebugx(1, "Got to reconnect!\n");
|
|
|
|
if (!reconnect_ups()) {
|
|
lastpoll = now;
|
|
dstate_datastale();
|
|
return;
|
|
}
|
|
|
|
hd = &curDevice;
|
|
|
|
if (hid_ups_walk(HU_WALKMODE_INIT) == FALSE) {
|
|
hd = NULL;
|
|
return;
|
|
}
|
|
}
|
|
#ifdef DEBUG
|
|
interval();
|
|
#endif
|
|
/* Get HID notifications on Interrupt pipe first */
|
|
if (use_interrupt_pipe == TRUE) {
|
|
evtCount = HIDGetEvents(udev, event, MAX_EVENT_NUM);
|
|
switch (evtCount)
|
|
{
|
|
case -EBUSY: /* Device or resource busy */
|
|
upslog_with_errno(LOG_CRIT, "Got disconnected by another driver");
|
|
case -EPERM: /* Operation not permitted */
|
|
case -ENODEV: /* No such device */
|
|
case -EACCES: /* Permission denied */
|
|
case -EIO: /* I/O error */
|
|
case -ENXIO: /* No such device or address */
|
|
case -ENOENT: /* No such file or directory */
|
|
/* Uh oh, got to reconnect! */
|
|
hd = NULL;
|
|
return;
|
|
default:
|
|
upsdebugx(1, "Got %i HID objects...", (evtCount >= 0) ? evtCount : 0);
|
|
break;
|
|
}
|
|
} else {
|
|
evtCount = 0;
|
|
upsdebugx(1, "Not using interrupt pipe...");
|
|
}
|
|
|
|
/* Process pending events (HID notifications on Interrupt pipe) */
|
|
for (i = 0; i < evtCount; i++) {
|
|
|
|
if (HIDGetDataValue(udev, event[i], &value, poll_interval) != 1)
|
|
continue;
|
|
|
|
if (nut_debug_level >= 2) {
|
|
upsdebugx(2, "Path: %s, Type: %s, ReportID: 0x%02x, Offset: %i, Size: %i, Value: %g",
|
|
HIDGetDataItem(event[i], subdriver->utab),
|
|
HIDDataType(event[i]), event[i]->ReportID,
|
|
event[i]->Offset, event[i]->Size, value);
|
|
}
|
|
|
|
/* Skip Input reports, if we don't use the Feature report */
|
|
item = find_hid_info(FindObject_with_Path(pDesc, &(event[i]->Path), interrupt_only ? ITEM_INPUT:ITEM_FEATURE));
|
|
if (!item) {
|
|
upsdebugx(3, "NUT doesn't use this HID object");
|
|
continue;
|
|
}
|
|
|
|
ups_infoval_set(item, value);
|
|
}
|
|
#ifdef DEBUG
|
|
upsdebugx(1, "took %.3f seconds handling interrupt reports...\n", interval());
|
|
#endif
|
|
/* clear status buffer before begining */
|
|
status_init();
|
|
|
|
/* Do a full update (polling) every pollfreq or upon data change (ie setvar/instcmd) */
|
|
if ((now > (lastpoll + pollfreq)) || (data_has_changed == TRUE)) {
|
|
upsdebugx(1, "Full update...");
|
|
|
|
alarm_init();
|
|
|
|
if (hid_ups_walk(HU_WALKMODE_FULL_UPDATE) == FALSE)
|
|
return;
|
|
|
|
lastpoll = now;
|
|
data_has_changed = FALSE;
|
|
|
|
ups_alarm_set();
|
|
alarm_commit();
|
|
} else {
|
|
upsdebugx(1, "Quick update...");
|
|
|
|
/* Quick poll data only to see if the UPS is still connected */
|
|
if (hid_ups_walk(HU_WALKMODE_QUICK_UPDATE) == FALSE)
|
|
return;
|
|
}
|
|
|
|
ups_status_set();
|
|
status_commit();
|
|
|
|
dstate_dataok();
|
|
#ifdef DEBUG
|
|
upsdebugx(1, "took %.3f seconds handling feature reports...\n", interval());
|
|
#endif
|
|
}
|
|
|
|
void upsdrv_initinfo(void)
|
|
{
|
|
char *val;
|
|
|
|
upsdebugx(1, "upsdrv_initinfo...");
|
|
|
|
dstate_setinfo("driver.version.data", "%s", subdriver->name);
|
|
|
|
/* init polling frequency */
|
|
val = getval(HU_VAR_POLLFREQ);
|
|
if (val) {
|
|
pollfreq = atoi(val);
|
|
}
|
|
|
|
dstate_setinfo("driver.parameter.pollfreq", "%d", pollfreq);
|
|
|
|
/* ignore (broken) interrupt pipe */
|
|
if (testvar("pollonly")) {
|
|
use_interrupt_pipe = FALSE;
|
|
}
|
|
|
|
time(&lastpoll);
|
|
|
|
/* install handlers */
|
|
upsh.setvar = setvar;
|
|
upsh.instcmd = instcmd;
|
|
}
|
|
|
|
void upsdrv_initups(void)
|
|
{
|
|
int ret;
|
|
char *val;
|
|
#ifdef SHUT_MODE
|
|
/*!
|
|
* SHUT is a serial protocol, so it needs
|
|
* only the device path
|
|
*/
|
|
upsdebugx(1, "upsdrv_initups...");
|
|
|
|
subdriver_matcher = device_path;
|
|
#else
|
|
char *regex_array[6];
|
|
|
|
upsdebugx(1, "upsdrv_initups...");
|
|
|
|
subdriver_matcher = &subdriver_matcher_struct;
|
|
|
|
/* enforce use of the "vendorid" option if "explore" is given */
|
|
if (testvar("explore") && getval("vendorid")==NULL) {
|
|
fatalx(EXIT_FAILURE, "must specify \"vendorid\" when using \"explore\"");
|
|
}
|
|
|
|
/* Activate maxreport tweak */
|
|
if (testvar("maxreport")) {
|
|
max_report_size = 1;
|
|
}
|
|
|
|
/* process the UPS selection options */
|
|
regex_array[0] = getval("vendorid");
|
|
regex_array[1] = getval("productid");
|
|
regex_array[2] = getval("vendor");
|
|
regex_array[3] = getval("product");
|
|
regex_array[4] = getval("serial");
|
|
regex_array[5] = getval("bus");
|
|
|
|
ret = USBNewRegexMatcher(®ex_matcher, regex_array, REG_ICASE | REG_EXTENDED);
|
|
switch(ret)
|
|
{
|
|
case 0:
|
|
break;
|
|
case -1:
|
|
fatal_with_errno(EXIT_FAILURE, "HIDNewRegexMatcher()");
|
|
default:
|
|
fatalx(EXIT_FAILURE, "invalid regular expression: %s", regex_array[ret]);
|
|
}
|
|
|
|
/* link the matchers */
|
|
subdriver_matcher->next = regex_matcher;
|
|
#endif /* SHUT_MODE */
|
|
|
|
/* Search for the first supported UPS matching the
|
|
regular expression (USB) or device_path (SHUT) */
|
|
ret = comm_driver->open(&udev, &curDevice, subdriver_matcher, &callback);
|
|
if (ret < 1)
|
|
fatalx(EXIT_FAILURE, "No matching HID UPS found");
|
|
|
|
hd = &curDevice;
|
|
|
|
upsdebugx(1, "Detected a UPS: %s/%s", hd->Vendor ? hd->Vendor : "unknown",
|
|
hd->Product ? hd->Product : "unknown");
|
|
|
|
/* Activate Powercom tweaks */
|
|
if (testvar("interruptonly")) {
|
|
interrupt_only = 1;
|
|
}
|
|
val = getval("interruptsize");
|
|
if (val) {
|
|
interrupt_size = atoi(val);
|
|
}
|
|
|
|
if (hid_ups_walk(HU_WALKMODE_INIT) == FALSE) {
|
|
fatalx(EXIT_FAILURE, "Can't initialize data from HID UPS");
|
|
}
|
|
|
|
if (dstate_getinfo("battery.charge.low")) {
|
|
/* Retrieve user defined battery settings */
|
|
val = getval(HU_VAR_LOWBATT);
|
|
if (val) {
|
|
dstate_setinfo("battery.charge.low", "%ld", strtol(val, NULL, 10));
|
|
}
|
|
}
|
|
|
|
if (dstate_getinfo("ups.delay.start")) {
|
|
/* Retrieve user defined delay settings */
|
|
val = getval(HU_VAR_ONDELAY);
|
|
if (val) {
|
|
dstate_setinfo("ups.delay.start", "%ld", strtol(val, NULL, 10));
|
|
}
|
|
}
|
|
|
|
if (dstate_getinfo("ups.delay.shutdown")) {
|
|
/* Retrieve user defined delay settings */
|
|
val = getval(HU_VAR_OFFDELAY);
|
|
if (val) {
|
|
dstate_setinfo("ups.delay.shutdown", "%ld", strtol(val, NULL, 10));
|
|
}
|
|
}
|
|
|
|
if (find_nut_info("load.off.delay")) {
|
|
/* Adds default with a delay value of '0' (= immediate) */
|
|
dstate_addcmd("load.off");
|
|
}
|
|
|
|
if (find_nut_info("load.on.delay")) {
|
|
/* Adds default with a delay value of '0' (= immediate) */
|
|
dstate_addcmd("load.on");
|
|
}
|
|
|
|
if (find_nut_info("load.off.delay") && find_nut_info("load.on.delay")) {
|
|
/* Add composite instcmds (require setting multiple HID values) */
|
|
dstate_addcmd("shutdown.return");
|
|
dstate_addcmd("shutdown.stayoff");
|
|
}
|
|
}
|
|
|
|
void upsdrv_cleanup(void)
|
|
{
|
|
upsdebugx(1, "upsdrv_cleanup...");
|
|
|
|
comm_driver->close(udev);
|
|
Free_ReportDesc(pDesc);
|
|
free_report_buffer(reportbuf);
|
|
#ifndef SHUT_MODE
|
|
USBFreeExactMatcher(exact_matcher);
|
|
USBFreeRegexMatcher(regex_matcher);
|
|
|
|
free(curDevice.Vendor);
|
|
free(curDevice.Product);
|
|
free(curDevice.Serial);
|
|
free(curDevice.Bus);
|
|
#endif
|
|
}
|
|
|
|
/**********************************************************************
|
|
* Support functions
|
|
*********************************************************************/
|
|
|
|
void possibly_supported(const char *mfr, HIDDevice_t *hd)
|
|
{
|
|
upsdebugx(0,
|
|
"This %s device (%04x:%04x) is not (or perhaps not yet) supported\n"
|
|
"by usbhid-ups. Please make sure you have an up-to-date version of NUT. If\n"
|
|
"this does not fix the problem, try running the driver with the\n"
|
|
"'-x productid=%04x' option. Please report your results to the NUT user's\n"
|
|
"mailing list <nut-upsuser@lists.alioth.debian.org>.\n",
|
|
mfr, hd->VendorID, hd->ProductID, hd->ProductID);
|
|
}
|
|
|
|
/* Update ups_status to remember this status item. Interpretation is
|
|
done in ups_status_set(). */
|
|
static void process_boolean_info(const char *nutvalue)
|
|
{
|
|
status_lkp_t *status_item;
|
|
int clear = 0;
|
|
|
|
upsdebugx(5, "process_boolean_info: %s", nutvalue);
|
|
|
|
if (*nutvalue == '!') {
|
|
nutvalue++;
|
|
clear = 1;
|
|
}
|
|
|
|
for (status_item = status_info; status_item->status_str != NULL ; status_item++)
|
|
{
|
|
if (strcasecmp(status_item->status_str, nutvalue))
|
|
continue;
|
|
|
|
if (clear) {
|
|
ups_status &= ~status_item->status_mask;
|
|
} else {
|
|
ups_status |= status_item->status_mask;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
upsdebugx(5, "Warning: %s not in list of known values", nutvalue);
|
|
}
|
|
|
|
static int callback(hid_dev_handle_t udev, HIDDevice_t *hd, unsigned char *rdbuf, int rdlen)
|
|
{
|
|
int i;
|
|
const char *mfr = NULL, *model = NULL, *serial = NULL;
|
|
#ifndef SHUT_MODE
|
|
int ret;
|
|
#endif
|
|
upsdebugx(2, "Report Descriptor size = %d", rdlen);
|
|
upsdebug_hex(3, "Report Descriptor", rdbuf, rdlen);
|
|
|
|
/* Parse Report Descriptor */
|
|
Free_ReportDesc(pDesc);
|
|
pDesc = Parse_ReportDesc(rdbuf, rdlen);
|
|
if (!pDesc) {
|
|
upsdebug_with_errno(1, "Failed to parse report descriptor!");
|
|
return 0;
|
|
}
|
|
|
|
/* prepare report buffer */
|
|
free_report_buffer(reportbuf);
|
|
reportbuf = new_report_buffer(pDesc);
|
|
if (!reportbuf) {
|
|
upsdebug_with_errno(1, "Failed to allocate report buffer!");
|
|
Free_ReportDesc(pDesc);
|
|
return 0;
|
|
}
|
|
|
|
/* select the subdriver for this device */
|
|
for (i=0; subdriver_list[i] != NULL; i++) {
|
|
if (subdriver_list[i]->claim(hd)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
subdriver = subdriver_list[i];
|
|
if (!subdriver) {
|
|
upsdebugx(1, "Manufacturer not supported!");
|
|
return 0;
|
|
}
|
|
|
|
upslogx(2, "Using subdriver: %s", subdriver->name);
|
|
|
|
HIDDumpTree(udev, subdriver->utab);
|
|
|
|
#ifndef SHUT_MODE
|
|
/* create a new matcher for later matching */
|
|
USBFreeExactMatcher(exact_matcher);
|
|
ret = USBNewExactMatcher(&exact_matcher, hd);
|
|
if (ret) {
|
|
upsdebug_with_errno(1, "USBNewExactMatcher()");
|
|
return 0;
|
|
}
|
|
|
|
regex_matcher->next = exact_matcher;
|
|
#endif /* SHUT_MODE */
|
|
|
|
/* apply subdriver specific formatting */
|
|
mfr = subdriver->format_mfr(hd);
|
|
model = subdriver->format_model(hd);
|
|
serial = subdriver->format_serial(hd);
|
|
|
|
if (mfr != NULL) {
|
|
dstate_setinfo("ups.mfr", "%s", mfr);
|
|
} else {
|
|
dstate_delinfo("ups.mfr");
|
|
}
|
|
|
|
if (model != NULL) {
|
|
dstate_setinfo("ups.model", "%s", model);
|
|
} else {
|
|
dstate_delinfo("ups.model");
|
|
}
|
|
|
|
if (serial != NULL) {
|
|
dstate_setinfo("ups.serial", "%s", serial);
|
|
} else {
|
|
dstate_delinfo("ups.serial");
|
|
}
|
|
|
|
dstate_setinfo("ups.vendorid", "%04x", hd->VendorID);
|
|
dstate_setinfo("ups.productid", "%04x", hd->ProductID);
|
|
|
|
return 1;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
static double interval(void)
|
|
{
|
|
struct timeval now;
|
|
static struct timeval last;
|
|
double ret;
|
|
|
|
gettimeofday(&now, NULL);
|
|
|
|
ret = now.tv_sec - last.tv_sec + ((double)(now.tv_usec - last.tv_usec)) / 1000000;
|
|
last = now;
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/* walk ups variables and set elements of the info array. */
|
|
static bool_t hid_ups_walk(walkmode_t mode)
|
|
{
|
|
hid_info_t *item;
|
|
double value;
|
|
int retcode;
|
|
|
|
/* 3 modes: HU_WALKMODE_INIT, HU_WALKMODE_QUICK_UPDATE and HU_WALKMODE_FULL_UPDATE */
|
|
|
|
/* Device data walk ----------------------------- */
|
|
for (item = subdriver->hid2nut; item->info_type != NULL; item++) {
|
|
|
|
#ifdef SHUT_MODE
|
|
/* Check if we are asked to stop (reactivity++) in SHUT mode.
|
|
* In USB mode, looping through this takes well under a second,
|
|
* so any effort to improve reactivity here is wasted. */
|
|
if (exit_flag != 0)
|
|
return TRUE;
|
|
#endif
|
|
/* filter data according to mode */
|
|
switch (mode)
|
|
{
|
|
/* Device capabilities enumeration */
|
|
case HU_WALKMODE_INIT:
|
|
/* Apparently, we are reconnecting, so
|
|
* NUT-to-HID translation is already good */
|
|
if (item->hiddata != NULL)
|
|
break;
|
|
|
|
/* Create the NUT-to-HID mapping */
|
|
item->hiddata = HIDGetItemData(item->hidpath, subdriver->utab);
|
|
if (item->hiddata == NULL)
|
|
continue;
|
|
|
|
/* Special case for handling server side variables */
|
|
if (item->hidflags & HU_FLAG_ABSENT) {
|
|
|
|
/* already set */
|
|
if (dstate_getinfo(item->info_type))
|
|
continue;
|
|
|
|
dstate_setinfo(item->info_type, "%s", item->dfl);
|
|
dstate_setflags(item->info_type, item->info_flags);
|
|
|
|
/* Set max length for strings, if needed */
|
|
if (item->info_flags & ST_FLAG_STRING)
|
|
dstate_setaux(item->info_type, item->info_len);
|
|
|
|
continue;
|
|
}
|
|
|
|
/* Allow duplicates for these NUT variables... */
|
|
if (!strncmp(item->info_type, "ups.alarm", 9)) {
|
|
break;
|
|
}
|
|
|
|
/* ...this one doesn't exist yet... */
|
|
if (dstate_getinfo(item->info_type) == NULL) {
|
|
break;
|
|
}
|
|
|
|
/* ...but this one does, so don't use it! */
|
|
item->hiddata = NULL;
|
|
continue;
|
|
|
|
case HU_WALKMODE_QUICK_UPDATE:
|
|
/* Quick update only deals with status and alarms! */
|
|
if (!(item->hidflags & HU_FLAG_QUICK_POLL))
|
|
continue;
|
|
|
|
break;
|
|
|
|
case HU_WALKMODE_FULL_UPDATE:
|
|
/* These don't need polling after initinfo() */
|
|
if (item->hidflags & (HU_FLAG_ABSENT | HU_TYPE_CMD | HU_FLAG_STATIC))
|
|
continue;
|
|
|
|
/* These need to be polled after user changes (setvar / instcmd) */
|
|
if ( (item->hidflags & HU_FLAG_SEMI_STATIC) && (data_has_changed == FALSE) )
|
|
continue;
|
|
|
|
break;
|
|
|
|
default:
|
|
fatalx(EXIT_FAILURE, "hid_ups_walk: unknown update mode!");
|
|
}
|
|
|
|
retcode = HIDGetDataValue(udev, item->hiddata, &value, poll_interval);
|
|
|
|
switch (retcode)
|
|
{
|
|
case -EBUSY: /* Device or resource busy */
|
|
upslog_with_errno(LOG_CRIT, "Got disconnected by another driver");
|
|
case -EPERM: /* Operation not permitted */
|
|
case -ENODEV: /* No such device */
|
|
case -EACCES: /* Permission denied */
|
|
case -EIO: /* I/O error */
|
|
case -ENXIO: /* No such device or address */
|
|
case -ENOENT: /* No such file or directory */
|
|
/* Uh oh, got to reconnect! */
|
|
hd = NULL;
|
|
return FALSE;
|
|
|
|
case 1:
|
|
break; /* Found! */
|
|
|
|
case 0:
|
|
continue;
|
|
|
|
case -ETIMEDOUT: /* Connection timed out */
|
|
case -EOVERFLOW: /* Value too large for defined data type */
|
|
#ifdef EPROTO
|
|
case -EPROTO: /* Protocol error */
|
|
#endif
|
|
case -EPIPE: /* Broken pipe */
|
|
default:
|
|
/* Don't know what happened, try again later... */
|
|
continue;
|
|
}
|
|
|
|
upsdebugx(2, "Path: %s, Type: %s, ReportID: 0x%02x, Offset: %i, Size: %i, Value: %g",
|
|
item->hidpath, HIDDataType(item->hiddata), item->hiddata->ReportID,
|
|
item->hiddata->Offset, item->hiddata->Size, value);
|
|
|
|
if (item->hidflags & HU_TYPE_CMD) {
|
|
dstate_addcmd(item->info_type);
|
|
continue;
|
|
}
|
|
|
|
/* Process the value we got back (set status bits and
|
|
* set the value of other parameters) */
|
|
if (ups_infoval_set(item, value) != 1)
|
|
continue;
|
|
|
|
if (mode == HU_WALKMODE_INIT) {
|
|
info_lkp_t *info_lkp;
|
|
|
|
dstate_setflags(item->info_type, item->info_flags);
|
|
|
|
/* Set max length for strings */
|
|
if (item->info_flags & ST_FLAG_STRING) {
|
|
dstate_setaux(item->info_type, item->info_len);
|
|
}
|
|
|
|
/* Set enumerated values, only if the data has ST_FLAG_RW */
|
|
if (!(item->hidflags & HU_FLAG_ENUM) || !(item->info_flags & ST_FLAG_RW)) {
|
|
continue;
|
|
}
|
|
|
|
/* Loop on all existing values */
|
|
for (info_lkp = item->hid2info; info_lkp != NULL
|
|
&& info_lkp->nut_value != NULL; info_lkp++) {
|
|
/* Check if this value is supported */
|
|
if (hu_find_infoval(item->hid2info, info_lkp->hid_value) != NULL) {
|
|
dstate_addenum(item->info_type, "%s", info_lkp->nut_value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static int reconnect_ups(void)
|
|
{
|
|
int ret;
|
|
|
|
upsdebugx(4, "==================================================");
|
|
upsdebugx(4, "= device has been disconnected, try to reconnect =");
|
|
upsdebugx(4, "==================================================");
|
|
|
|
ret = comm_driver->open(&udev, &curDevice, subdriver_matcher, NULL);
|
|
|
|
if (ret > 0) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Convert the local status information to NUT format and set NUT
|
|
alarms. */
|
|
static void ups_alarm_set(void)
|
|
{
|
|
if (ups_status & STATUS(REPLACEBATT)) {
|
|
alarm_set("Replace battery!");
|
|
}
|
|
if (ups_status & STATUS(SHUTDOWNIMM)) {
|
|
alarm_set("Shutdown imminent!");
|
|
}
|
|
if (ups_status & STATUS(FANFAIL)) {
|
|
alarm_set("Fan failure!");
|
|
}
|
|
if (ups_status & STATUS(NOBATTERY)) {
|
|
alarm_set("No battery installed!");
|
|
}
|
|
if (ups_status & STATUS(BATTVOLTLO)) {
|
|
alarm_set("Battery voltage too low!");
|
|
}
|
|
if (ups_status & STATUS(BATTVOLTHI)) {
|
|
alarm_set("Battery voltage too high!");
|
|
}
|
|
if (ups_status & STATUS(CHARGERFAIL)) {
|
|
alarm_set("Battery charger fail!");
|
|
}
|
|
if (ups_status & STATUS(OVERHEAT)) {
|
|
alarm_set("Temperature too high!"); /* overheat; Belkin, TrippLite */
|
|
}
|
|
if (ups_status & STATUS(COMMFAULT)) {
|
|
alarm_set("Internal UPS fault!"); /* UPS fault; Belkin, TrippLite */
|
|
}
|
|
if (ups_status & STATUS(AWAITINGPOWER)) {
|
|
alarm_set("Awaiting power!"); /* awaiting power; Belkin, TrippLite */
|
|
}
|
|
if (ups_status & STATUS(BYPASSAUTO)) {
|
|
alarm_set("Automatic bypass mode!");
|
|
}
|
|
if (ups_status & STATUS(BYPASSMAN)) {
|
|
alarm_set("Manual bypass mode!");
|
|
}
|
|
}
|
|
|
|
/* Convert the local status information to NUT format and set NUT
|
|
status. */
|
|
static void ups_status_set(void)
|
|
{
|
|
if (ups_status & STATUS(VRANGE)) {
|
|
dstate_setinfo("input.transfer.reason", "input voltage out of range");
|
|
} else if (ups_status & STATUS(FRANGE)) {
|
|
dstate_setinfo("input.transfer.reason", "input frequency out of range");
|
|
} else {
|
|
dstate_delinfo("input.transfer.reason");
|
|
}
|
|
|
|
if (ups_status & STATUS(ONLINE)) {
|
|
status_set("OL"); /* on line */
|
|
} else {
|
|
status_set("OB"); /* on battery */
|
|
}
|
|
if ((ups_status & STATUS(DISCHRG)) &&
|
|
!(ups_status & STATUS(DEPLETED))) {
|
|
status_set("DISCHRG"); /* discharging */
|
|
}
|
|
if ((ups_status & STATUS(CHRG)) &&
|
|
!(ups_status & STATUS(FULLYCHARGED))) {
|
|
status_set("CHRG"); /* charging */
|
|
}
|
|
if (ups_status & (STATUS(LOWBATT) | STATUS(TIMELIMITEXP) | STATUS(SHUTDOWNIMM))) {
|
|
status_set("LB"); /* low battery */
|
|
}
|
|
if (ups_status & STATUS(OVERLOAD)) {
|
|
status_set("OVER"); /* overload */
|
|
}
|
|
if (ups_status & STATUS(REPLACEBATT)) {
|
|
status_set("RB"); /* replace batt */
|
|
}
|
|
if (ups_status & STATUS(TRIM)) {
|
|
status_set("TRIM"); /* SmartTrim */
|
|
}
|
|
if (ups_status & STATUS(BOOST)) {
|
|
status_set("BOOST"); /* SmartBoost */
|
|
}
|
|
if (ups_status & (STATUS(BYPASSAUTO) | STATUS(BYPASSMAN))) {
|
|
status_set("BYPASS"); /* on bypass */
|
|
}
|
|
if (ups_status & STATUS(OFF)) {
|
|
status_set("OFF"); /* ups is off */
|
|
}
|
|
if (ups_status & STATUS(CAL)) {
|
|
status_set("CAL"); /* calibration */
|
|
}
|
|
}
|
|
|
|
/* find info element definition in info array
|
|
* by NUT varname.
|
|
*/
|
|
static hid_info_t *find_nut_info(const char *varname)
|
|
{
|
|
hid_info_t *hidups_item;
|
|
|
|
for (hidups_item = subdriver->hid2nut; hidups_item->info_type != NULL ; hidups_item++) {
|
|
|
|
if (strcasecmp(hidups_item->info_type, varname))
|
|
continue;
|
|
|
|
if (hidups_item->hiddata != NULL)
|
|
return hidups_item;
|
|
}
|
|
|
|
upsdebugx(2, "find_nut_info: unknown info type: %s", varname);
|
|
return NULL;
|
|
}
|
|
|
|
/* find info element definition in info array
|
|
* by HID data pointer.
|
|
*/
|
|
static hid_info_t *find_hid_info(const HIDData_t *hiddata)
|
|
{
|
|
hid_info_t *hidups_item;
|
|
|
|
for (hidups_item = subdriver->hid2nut; hidups_item->info_type != NULL ; hidups_item++) {
|
|
|
|
/* Skip server side vars */
|
|
if (hidups_item->hidflags & HU_FLAG_ABSENT)
|
|
continue;
|
|
|
|
if (hidups_item->hiddata == hiddata)
|
|
return hidups_item;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* find the HID Item value matching that NUT value */
|
|
/* useful for set with value lookup... */
|
|
static long hu_find_valinfo(info_lkp_t *hid2info, const char* value)
|
|
{
|
|
info_lkp_t *info_lkp;
|
|
|
|
/* if a conversion function is defined, use 'value' as argument for it */
|
|
if (hid2info->nuf != NULL) {
|
|
double hid_value;
|
|
hid_value = hid2info->nuf(value);
|
|
upsdebugx(5, "hu_find_valinfo: found %g (value: %s)", hid_value, value);
|
|
return hid_value;
|
|
}
|
|
|
|
for (info_lkp = hid2info; info_lkp->nut_value != NULL; info_lkp++) {
|
|
if (!(strcmp(info_lkp->nut_value, value))) {
|
|
upsdebugx(5, "hu_find_valinfo: found %s (value: %ld)", info_lkp->nut_value, info_lkp->hid_value);
|
|
return info_lkp->hid_value;
|
|
}
|
|
}
|
|
|
|
upsdebugx(3, "hu_find_valinfo: no matching HID value for this INFO_* value (%s)", value);
|
|
return -1;
|
|
}
|
|
|
|
/* find the NUT value matching that HID Item value */
|
|
static const char *hu_find_infoval(info_lkp_t *hid2info, const double value)
|
|
{
|
|
info_lkp_t *info_lkp;
|
|
|
|
/* if a conversion function is defined, use 'value' as argument for it */
|
|
if (hid2info->fun != NULL) {
|
|
return hid2info->fun(value);
|
|
}
|
|
|
|
/* use 'value' as an index for a lookup in an array */
|
|
for (info_lkp = hid2info; info_lkp->nut_value != NULL; info_lkp++) {
|
|
if (info_lkp->hid_value == (long)value) {
|
|
upsdebugx(5, "hu_find_infoval: found %s (value: %ld)", info_lkp->nut_value, (long)value);
|
|
return info_lkp->nut_value;
|
|
}
|
|
}
|
|
|
|
upsdebugx(3, "hu_find_infoval: no matching INFO_* value for this HID value (%g)", value);
|
|
return NULL;
|
|
}
|
|
|
|
/* return -1 on failure, 0 for a status update and 1 in all other cases */
|
|
static int ups_infoval_set(hid_info_t *item, double value)
|
|
{
|
|
const char *nutvalue;
|
|
|
|
/* need lookup'ed translation? */
|
|
if (item->hid2info != NULL){
|
|
|
|
if ((nutvalue = hu_find_infoval(item->hid2info, value)) == NULL) {
|
|
upsdebugx(5, "Lookup [%g] failed for [%s]", value, item->info_type);
|
|
return -1;
|
|
}
|
|
|
|
/* deal with boolean items */
|
|
if (!strncmp(item->info_type, "BOOL", 4)) {
|
|
process_boolean_info(nutvalue);
|
|
return 0;
|
|
}
|
|
|
|
/* deal with alarm items */
|
|
if (!strncmp(item->info_type, "ups.alarm", 9)) {
|
|
alarm_set(nutvalue);
|
|
return 0;
|
|
}
|
|
|
|
dstate_setinfo(item->info_type, "%s", nutvalue);
|
|
} else {
|
|
dstate_setinfo(item->info_type, item->dfl, value);
|
|
}
|
|
|
|
return 1;
|
|
}
|