Imported Upstream version 2.4.3
This commit is contained in:
commit
26fb71b504
446 changed files with 148951 additions and 0 deletions
235
drivers/Makefile.am
Normal file
235
drivers/Makefile.am
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
# Network UPS Tools: drivers
|
||||
|
||||
# by default, link programs in this directory with libcommon.a
|
||||
#FIXME: SERLIBS is only useful for LDADD_DRIVERS_SERIAL not for LDADD_COMMON
|
||||
LDADD_COMMON = ../common/libcommon.a ../common/libparseconf.la
|
||||
LDADD_DRIVERS = $(LDADD_COMMON) main.o dstate.o
|
||||
LDADD_HAL_DRIVERS = $(LDADD_COMMON) libnuthalmain.a
|
||||
LDADD_DRIVERS_SERIAL = $(LDADD_DRIVERS) $(SERLIBS) serial.o
|
||||
|
||||
# most targets are drivers, so make this the default
|
||||
LDADD = $(LDADD_DRIVERS_SERIAL)
|
||||
# Avoid per-target CFLAGS, because this will prevent re-use of object
|
||||
# files. In any case, CFLAGS are only -I options, so there is no harm,
|
||||
# but only add them if we really use the target.
|
||||
AM_CFLAGS = -I$(top_srcdir)/include
|
||||
if WITH_HAL
|
||||
AM_CFLAGS += $(LIBHAL_CFLAGS)
|
||||
endif
|
||||
if WITH_USB
|
||||
AM_CFLAGS += $(LIBUSB_CFLAGS)
|
||||
endif
|
||||
if WITH_SNMP
|
||||
AM_CFLAGS += $(LIBNETSNMP_CFLAGS)
|
||||
endif
|
||||
if WITH_NEONXML
|
||||
AM_CFLAGS += $(LIBNEON_CFLAGS)
|
||||
endif
|
||||
if WITH_LIBPOWERMAN
|
||||
AM_CFLAGS += $(LIBPOWERMAN_CFLAGS)
|
||||
endif
|
||||
|
||||
SERIAL_DRIVERLIST = apcsmart bcmxcp belkin belkinunv bestfcom \
|
||||
bestfortress bestuferrups bestups dummy-ups etapro everups \
|
||||
gamatronic genericups isbmex liebert liebertgxt2 masterguard megatec metasys \
|
||||
mge-shut mge-utalk microdowell newmge-shut oneac optiups powercom rhino \
|
||||
safenet skel solis tripplite tripplitesu upscode2 victronups powerpanel \
|
||||
blazer_ser clone clone-outlet ivtscd
|
||||
SNMP_DRIVERLIST = snmp-ups
|
||||
USB_LIBUSB_DRIVERLIST = usbhid-ups bcmxcp_usb tripplite_usb megatec_usb \
|
||||
blazer_usb richcomm_usb
|
||||
USB_DRIVERLIST = $(USB_LIBUSB_DRIVERLIST)
|
||||
HAL_DRIVERLIST = hald-addon-usbhid-ups hald-addon-bcmxcp_usb \
|
||||
hald-addon-tripplite_usb hald-addon-megatec_usb
|
||||
NEONXML_DRIVERLIST = netxml-ups
|
||||
|
||||
# distribute all drivers, even ones that are not built by default
|
||||
EXTRA_PROGRAMS = $(SERIAL_DRIVERLIST) $(SNMP_DRIVERLIST) $(USB_DRIVERLIST) $(NEONXML_DRIVERLIST)
|
||||
|
||||
# construct the list of drivers to build
|
||||
if SOME_DRIVERS
|
||||
driverexec_PROGRAMS = $(DRIVER_BUILD_LIST)
|
||||
else
|
||||
driverexec_PROGRAMS =
|
||||
if WITH_SERIAL
|
||||
driverexec_PROGRAMS += $(SERIAL_DRIVERLIST)
|
||||
endif
|
||||
if WITH_SNMP
|
||||
driverexec_PROGRAMS += $(SNMP_DRIVERLIST)
|
||||
endif
|
||||
if WITH_USB
|
||||
driverexec_PROGRAMS += $(USB_LIBUSB_DRIVERLIST)
|
||||
endif
|
||||
if WITH_HAL
|
||||
halexecdir = $(HAL_CALLOUTS_PATH)
|
||||
halexec_PROGRAMS = $(HAL_DRIVERLIST)
|
||||
endif
|
||||
if WITH_NEONXML
|
||||
driverexec_PROGRAMS += $(NEONXML_DRIVERLIST)
|
||||
endif
|
||||
if WITH_LIBPOWERMAN
|
||||
driverexec_PROGRAMS += powerman-pdu
|
||||
endif
|
||||
else
|
||||
driverexec_PROGRAMS += skel
|
||||
endif
|
||||
|
||||
# always build upsdrvctl
|
||||
driverexec_PROGRAMS += upsdrvctl
|
||||
|
||||
# ==========================================================================
|
||||
# Driver build details
|
||||
|
||||
# upsdrvctl: the all-singing all-dancing driver control program
|
||||
upsdrvctl_SOURCES = upsdrvctl.c
|
||||
upsdrvctl_LDADD = $(LDADD_COMMON)
|
||||
|
||||
# serial drivers: all of them use standard LDADD and CFLAGS
|
||||
apcsmart_SOURCES = apcsmart.c
|
||||
bcmxcp_SOURCES = bcmxcp.c bcmxcp_ser.c
|
||||
belkin_SOURCES = belkin.c
|
||||
belkinunv_SOURCES = belkinunv.c
|
||||
bestfcom_SOURCES = bestfcom.c
|
||||
bestuferrups_SOURCES = bestuferrups.c
|
||||
bestups_SOURCES = bestups.c
|
||||
blazer_ser_SOURCES = blazer.c blazer_ser.c
|
||||
blazer_ser_LDADD = $(LDADD) -lm
|
||||
etapro_SOURCES = etapro.c
|
||||
everups_SOURCES = everups.c
|
||||
gamatronic_SOURCES = gamatronic.c
|
||||
genericups_SOURCES = genericups.c
|
||||
isbmex_SOURCES = isbmex.c
|
||||
isbmex_LDADD = $(LDADD) -lm
|
||||
ivtscd_SOURCES = ivtscd.c
|
||||
liebert_SOURCES = liebert.c
|
||||
liebertgxt2_SOURCES = liebertgxt2.c
|
||||
masterguard_SOURCES = masterguard.c
|
||||
megatec_SOURCES = megatec.c
|
||||
metasys_SOURCES = metasys.c
|
||||
mge_shut_SOURCES = mge-shut.c hidparser.c
|
||||
mge_utalk_SOURCES = mge-utalk.c
|
||||
microdowell_SOURCES = microdowell.c
|
||||
oneac_SOURCES = oneac.c
|
||||
optiups_SOURCES = optiups.c
|
||||
powercom_SOURCES = powercom.c
|
||||
powercom_LDADD = $(LDADD) -lm
|
||||
powerpanel_SOURCES = powerpanel.c powerp-bin.c powerp-txt.c
|
||||
powerpanel_LDADD = $(LDADD) -lm
|
||||
rhino_SOURCES = rhino.c
|
||||
rhino_LDADD = $(LDADD) -lm
|
||||
safenet_SOURCES = safenet.c
|
||||
solis_SOURCES = solis.c
|
||||
tripplite_SOURCES = tripplite.c
|
||||
tripplite_LDADD = $(LDADD) -lm
|
||||
tripplitesu_SOURCES = tripplitesu.c
|
||||
upscode2_SOURCES = upscode2.c
|
||||
upscode2_LDADD = $(LDADD) -lm
|
||||
victronups_SOURCES = victronups.c
|
||||
|
||||
# non-serial drivers: these use custom LDADD and/or CFLAGS
|
||||
|
||||
# dummy
|
||||
dummy_ups_SOURCES = dummy-ups.c
|
||||
dummy_ups_CFLAGS = $(AM_CFLAGS) -I$(top_srcdir)/clients
|
||||
dummy_ups_LDADD = $(LDADD_DRIVERS) ../clients/libupsclient.la
|
||||
if WITH_SSL
|
||||
dummy_ups_CFLAGS += $(LIBSSL_CFLAGS)
|
||||
dummy_ups_LDADD += $(LIBSSL_LDFLAGS)
|
||||
endif
|
||||
|
||||
# Clone drivers
|
||||
clone_SOURCES = clone.c
|
||||
clone_outlet_SOURCES = clone-outlet.c
|
||||
|
||||
# sample skeleton driver
|
||||
|
||||
skel_SOURCES = skel.c
|
||||
skel_LDADD = $(LDADD_DRIVERS)
|
||||
|
||||
# USB
|
||||
USBHID_UPS_SUBDRIVERS = apc-hid.c belkin-hid.c cps-hid.c explore-hid.c \
|
||||
liebert-hid.c mge-hid.c powercom-hid.c tripplite-hid.c
|
||||
usbhid_ups_SOURCES = usbhid-ups.c libhid.c libusb.c hidparser.c \
|
||||
usb-common.c $(USBHID_UPS_SUBDRIVERS)
|
||||
usbhid_ups_LDADD = $(LDADD_DRIVERS) $(LIBUSB_LDFLAGS)
|
||||
|
||||
tripplite_usb_SOURCES = tripplite_usb.c libusb.c usb-common.c
|
||||
tripplite_usb_LDADD = $(LDADD_DRIVERS) $(LIBUSB_LDFLAGS) -lm
|
||||
|
||||
bcmxcp_usb_SOURCES = bcmxcp_usb.c bcmxcp.c usb-common.c
|
||||
bcmxcp_usb_LDADD = $(LDADD_DRIVERS) $(LIBUSB_LDFLAGS)
|
||||
|
||||
megatec_usb_SOURCES = megatec.c megatec_usb.c libusb.c usb-common.c
|
||||
megatec_usb_CFLAGS = $(AM_CFLAGS) -DMEGATEC_SUBDRV
|
||||
megatec_usb_LDADD = $(LDADD_DRIVERS) $(LIBUSB_LDFLAGS)
|
||||
|
||||
blazer_usb_SOURCES = blazer.c blazer_usb.c libusb.c usb-common.c
|
||||
blazer_usb_LDADD = $(LDADD_DRIVERS) $(LIBUSB_LDFLAGS) -lm
|
||||
|
||||
richcomm_usb_SOURCES = richcomm_usb.c usb-common.c
|
||||
richcomm_usb_LDADD = $(LDADD_DRIVERS) $(LIBUSB_LDFLAGS)
|
||||
|
||||
# HID-over-serial
|
||||
newmge_shut_SOURCES = usbhid-ups.c libshut.c libhid.c hidparser.c mge-hid.c
|
||||
# per-target CFLAGS are necessary here
|
||||
newmge_shut_CFLAGS = $(AM_CFLAGS) -DSHUT_MODE
|
||||
newmge_shut_LDADD = $(LDADD)
|
||||
|
||||
# SNMP
|
||||
snmp_ups_SOURCES = snmp-ups.c apc-mib.c baytech-mib.c compaq-mib.c eaton-mib.c \
|
||||
ietf-mib.c mge-mib.c netvision-mib.c powerware-mib.c raritan-pdu-mib.c
|
||||
snmp_ups_LDADD = $(LDADD_DRIVERS) $(LIBNETSNMP_LDFLAGS)
|
||||
|
||||
# HAL
|
||||
hald_addon_usbhid_ups_SOURCES = usbhid-ups.c libhid.c libusb.c hidparser.c \
|
||||
$(USBHID_UPS_SUBDRIVERS)
|
||||
hald_addon_usbhid_ups_LDADD = $(LDADD_HAL_DRIVERS) $(LIBUSB_LDFLAGS) $(LIBHAL_LDFLAGS)
|
||||
|
||||
hald_addon_tripplite_usb_SOURCES = tripplite_usb.c libusb.c
|
||||
hald_addon_tripplite_usb_LDADD = $(LDADD_HAL_DRIVERS) $(LIBUSB_LDFLAGS) $(LIBHAL_LDFLAGS) -lm
|
||||
|
||||
hald_addon_bcmxcp_usb_SOURCES = bcmxcp_usb.c bcmxcp.c
|
||||
hald_addon_bcmxcp_usb_LDADD = $(LDADD_HAL_DRIVERS) $(LIBUSB_LDFLAGS) $(LIBHAL_LDFLAGS)
|
||||
|
||||
hald_addon_megatec_usb_SOURCES = megatec.c megatec_usb.c libusb.c
|
||||
hald_addon_megatec_usb_CFLAGS = $(AM_CFLAGS) -DMEGATEC_SUBDRV
|
||||
hald_addon_megatec_usb_LDADD = $(LDADD_HAL_DRIVERS) $(LIBUSB_LDFLAGS) $(LIBHAL_LDFLAGS)
|
||||
|
||||
# NEON XML/HTTP
|
||||
netxml_ups_SOURCES = netxml-ups.c mge-xml.c
|
||||
netxml_ups_LDADD = $(LDADD_DRIVERS) $(LIBNEON_LDFLAGS)
|
||||
|
||||
# Powerman
|
||||
powerman_pdu_SOURCES = powerman-pdu.c
|
||||
powerman_pdu_LDADD = $(LDADD) $(LIBPOWERMAN_LDFLAGS)
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# List of header files. The purpose of this list is not dependency
|
||||
# tracking (which is automatic), but to ensure these files are
|
||||
# distributed by "make dist".
|
||||
|
||||
dist_noinst_HEADERS = apc-mib.h apc-hid.h apcsmart.h baytech-mib.h bcmxcp.h \
|
||||
bcmxcp_io.h belkin.h belkin-hid.h blazer.h cps-hid.h dstate.h dstate-hal.h \
|
||||
dummy-ups.h eaton-mib.h explore-hid.h gamatronic.h genericups.h \
|
||||
hidparser.h hidtypes.h ietf-mib.h libhid.h libshut.h libusb.h liebert-hid.h \
|
||||
main.h main-hal.h megatec.h mge-hid.h mge-mib.h mge-shut.h mge-utalk.h \
|
||||
mge-xml.h microdowell.h netvision-mib.h netxml-ups.h oneac.h \
|
||||
powercom.h powerpanel.h powerp-bin.h powerp-txt.h powerware-mib.h raritan-pdu-mib.h \
|
||||
safenet.h serial.h snmp-ups.h solis.h tripplite.h tripplite-hid.h \
|
||||
upshandler.h usb-common.h usbhid-ups.h powercom-hid.h compaq-mib.h
|
||||
|
||||
# Define a dummy library so that Automake builds rules for the
|
||||
# corresponding object files. This library is not actually built,
|
||||
EXTRA_LIBRARIES = libdummy.a
|
||||
libdummy_a_SOURCES = main.c dstate.c serial.c
|
||||
|
||||
# the nuthalmain library combines the code for main-hal.c and
|
||||
# dstate-hal.c. It is necessary for Automake-technical reasons,
|
||||
# because per-object CFLAGS can only be specified for libraries, not
|
||||
# for object files. This library is used during the build process,
|
||||
# and is not meant to be installed.
|
||||
|
||||
EXTRA_LIBRARIES += libnuthalmain.a
|
||||
libnuthalmain_a_SOURCES = main-hal.c dstate-hal.c usb-common.c
|
||||
|
||||
MOSTLYCLEANFILES = libnuthalmain.a
|
||||
1570
drivers/Makefile.in
Normal file
1570
drivers/Makefile.in
Normal file
File diff suppressed because it is too large
Load diff
401
drivers/apc-hid.c
Normal file
401
drivers/apc-hid.c
Normal file
|
|
@ -0,0 +1,401 @@
|
|||
/* apc-hid.c - data to monitor APC USB/HID devices with NUT
|
||||
*
|
||||
* Copyright (C)
|
||||
* 2003 - 2009 Arnaud Quette <arnaud.quette@free.fr>
|
||||
* 2005 John Stamp <kinsayder@hotmail.com>
|
||||
* 2005 Peter Selinger <selinger@users.sourceforge.net>
|
||||
* 2009 Arjen de Korte <adkorte-guest@alioth.debian.org>
|
||||
*
|
||||
* Sponsored by MGE UPS SYSTEMS <http://www.mgeups.com>
|
||||
* and Eaton <http://www.eaton.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "main.h" /* for getval() */
|
||||
#include "usbhid-ups.h"
|
||||
#include "apc-hid.h"
|
||||
#include "usb-common.h"
|
||||
|
||||
#define APC_HID_VERSION "APC HID 0.95"
|
||||
|
||||
/* APC */
|
||||
#define APC_VENDORID 0x051d
|
||||
|
||||
/* USB IDs device table */
|
||||
static usb_device_id_t apc_usb_device_table[] = {
|
||||
/* various models */
|
||||
{ USB_DEVICE(APC_VENDORID, 0x0002), NULL },
|
||||
|
||||
/* Terminating entry */
|
||||
{ -1, -1, NULL }
|
||||
};
|
||||
|
||||
/* returns statically allocated string - must not use it again before
|
||||
done with result! */
|
||||
static char *apc_date_conversion_fun(double value)
|
||||
{
|
||||
static char buf[20];
|
||||
int year, month, day;
|
||||
|
||||
if ((long)value == 0) {
|
||||
return "not set";
|
||||
}
|
||||
|
||||
/* APC apparently uses a hexadecimal-as-decimal format, e.g.,
|
||||
0x102202 = October 22, 2002 */
|
||||
year = ((long)value & 0xf) + 10 * (((long)value>>4) & 0xf);
|
||||
month = (((long)value>>16) & 0xf) + 10 * (((long)value>>20) & 0xf);
|
||||
day = (((long)value>>8) & 0xf) + 10 * (((long)value>>12) & 0xf);
|
||||
|
||||
/* Y2K conversion - hope that this format will be retired before 2070 :) */
|
||||
if (year >= 70) {
|
||||
year += 1900;
|
||||
} else {
|
||||
year += 2000;
|
||||
}
|
||||
|
||||
snprintf(buf, sizeof(buf), "%04d/%02d/%02d", year, month, day);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
info_lkp_t apc_date_conversion[] = {
|
||||
{ 0, NULL, apc_date_conversion_fun }
|
||||
};
|
||||
|
||||
/* This was determined empirically from observing a BackUPS LS 500 */
|
||||
static info_lkp_t apcstatusflag_info[] = {
|
||||
{ 8, "!off", NULL }, /* Normal operation */
|
||||
{ 16, "!off", NULL }, /* This occurs briefly during power-on, and corresponds to status 'DISCHRG'. */
|
||||
{ 0, "off", NULL },
|
||||
{ 0, NULL, NULL }
|
||||
};
|
||||
|
||||
/* Reason of the last battery transfer (from apcupsd) */
|
||||
static info_lkp_t apc_linefailcause_vrange_info[] = {
|
||||
{ 1, "vrange", NULL }, /* Low line voltage */
|
||||
{ 2, "vrange", NULL }, /* High line voltage */
|
||||
{ 4, "vrange", NULL }, /* notch, spike, or blackout */
|
||||
{ 8, "vrange", NULL }, /* Notch or blackout */
|
||||
{ 9, "vrange", NULL }, /* Spike or blackout */
|
||||
{ 0, "!vrange", NULL }, /* No transfers have ocurred */
|
||||
{ 0, NULL, NULL }
|
||||
};
|
||||
|
||||
static info_lkp_t apc_linefailcause_frange_info[] = {
|
||||
{ 7, "frange", NULL }, /* Input frequency out of range */
|
||||
{ 0, "!frange", NULL }, /* No transfers have ocurred */
|
||||
{ 0, NULL, NULL }
|
||||
};
|
||||
|
||||
#if 0
|
||||
/* these input.transfer.reason can't be mapped at the moment... */
|
||||
{ 3, "ripple", NULL }, /* Ripple */
|
||||
{ 5, "self test", NULL }, /* Self Test or Discharge Calibration commanded
|
||||
* Test usage, front button, or 2 week self test */
|
||||
{ 6, "forced", NULL }, /* DelayBeforeShutdown or APCDelayBeforeShutdown */
|
||||
{ 10, "forced", NULL }, /* Graceful shutdown by accessories */
|
||||
{ 11, "self test", NULL }, /* Test usage invoked */
|
||||
{ 12, "self test", NULL }, /* Front button initiated self test */
|
||||
{ 13, "self test", NULL }, /* 2 week self test */
|
||||
{ 0, NULL, NULL }
|
||||
#endif
|
||||
|
||||
static info_lkp_t apc_sensitivity_info[] = {
|
||||
{ 0, "low", NULL },
|
||||
{ 1, "medium", NULL },
|
||||
{ 2, "high", NULL },
|
||||
{ 0, NULL, NULL }
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* Vendor-specific usage table */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
/* APC usage table */
|
||||
static usage_lkp_t apc_usage_lkp[] = {
|
||||
{ "APCGeneralCollection", 0xff860005 },
|
||||
{ "APCEnvironment", 0xff860006 },
|
||||
{ "APCProbe1", 0xff860007 },
|
||||
{ "APCProbe2", 0xff860008 },
|
||||
{ "APCBattReplaceDate", 0xff860016 },
|
||||
{ "APCBattCapBeforeStartup", 0xff860019 }, /* FIXME: exploit */
|
||||
{ "APC_UPS_FirmwareRevision", 0xff860042 },
|
||||
{ "APCLineFailCause", 0xff860052 },
|
||||
{ "APCStatusFlag", 0xff860060 },
|
||||
{ "APCSensitivity", 0xff860061 },
|
||||
/* usage seen in dumps but unknown:
|
||||
* - ff860027, ff860028
|
||||
* Path: UPS.ff860027, Type: Feature, ReportID: 0x3e, Offset: 0,
|
||||
* Size: 32, Value:0.000000
|
||||
*/
|
||||
{ "APCPanelTest", 0xff860072 }, /* FIXME: exploit */
|
||||
{ "APCShutdownAfterDelay", 0xff860076 }, /* FIXME: exploit */
|
||||
{ "APC_USB_FirmwareRevision", 0xff860079 }, /* FIXME: exploit */
|
||||
{ "APCDelayBeforeReboot", 0xff86007c },
|
||||
{ "APCDelayBeforeShutdown", 0xff86007d },
|
||||
{ "APCDelayBeforeStartup", 0xff86007e }, /* FIXME: exploit */
|
||||
/* usage seen in dumps but unknown:
|
||||
* - ff860080
|
||||
* Path: UPS.PresentStatus.ff860080, Type: Input, ReportID: 0x33,
|
||||
* Offset: 12, Size: 1, Value: 0.000000
|
||||
* - ff86001a
|
||||
* Path: UPS.Battery.ff86001a, Type: Input, ReportID: 0x1b,
|
||||
* Offset: 0, Size: 8, Value: 3.000000
|
||||
* - ff86001b
|
||||
* Path: UPS.Battery.ff86001b, Type: Input, ReportID: 0x1c,
|
||||
* Offset: 0, Size: 8, Value: 0.000000
|
||||
*/
|
||||
|
||||
/* Note (Arnaud): BUP stands for BackUPS Pro
|
||||
* This is a HID uncompliant special (manufacturer) collection
|
||||
* FIXME: these need to be used... */
|
||||
{ "BUPHibernate", 0x00850058 }, /* FIXME: exploit */
|
||||
{ "BUPBattCapBeforeStartup", 0x00860012 }, /* FIXME: exploit */
|
||||
{ "BUPDelayBeforeStartup", 0x00860076 }, /* FIXME: exploit */
|
||||
{ "BUPSelfTest", 0x00860010 }, /* FIXME: exploit */
|
||||
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
/*
|
||||
* USB USAGE NOTES for APC (from Russell Kroll in the old hidups)
|
||||
*
|
||||
* FIXME: read 0xff86.... instead of 0x(00)86....?
|
||||
*
|
||||
* 0x860013 == 44200155090 - capability again
|
||||
* == locale 4, 4 choices, 2 bytes, 00, 15, 50, 90
|
||||
* == minimum charge to return online
|
||||
*
|
||||
* 0x860060 == "441HMLL" - looks like a 'capability' string
|
||||
* == locale 4, 4 choices, 1 byte each
|
||||
* == line sensitivity (high, medium, low, low)
|
||||
* NOTE! the above does not seem to correspond to my info
|
||||
*
|
||||
* 0x860062 == D43133136127130
|
||||
* == locale D, 4 choices, 3 bytes, 133, 136, 127, 130
|
||||
* == high transfer voltage
|
||||
*
|
||||
* 0x860064 == D43103100097106
|
||||
* == locale D, 4 choices, 3 bytes, 103, 100, 097, 106
|
||||
* == low transfer voltage
|
||||
*
|
||||
* 0x860066 == 441HMLL (see 860060)
|
||||
*
|
||||
* 0x860074 == 4410TLN
|
||||
* == locale 4, 4 choices, 1 byte, 0, T, L, N
|
||||
* == alarm setting (5s, 30s, low battery, none)
|
||||
*
|
||||
* 0x860077 == 443060180300600
|
||||
* == locale 4, 4 choices, 3 bytes, 060,180,300,600
|
||||
* == wake-up delay (after power returns)
|
||||
*/
|
||||
|
||||
static usage_tables_t apc_utab[] = {
|
||||
apc_usage_lkp,
|
||||
hid_usage_lkp,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* HID2NUT lookup table */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
/* HID2NUT lookup table */
|
||||
static hid_info_t apc_hid2nut[] = {
|
||||
/* Battery page */
|
||||
{ "battery.charge", 0, 0, "UPS.PowerSummary.RemainingCapacity", NULL, "%.0f", 0, NULL },
|
||||
{ "battery.charge.low", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.PowerSummary.RemainingCapacityLimit", NULL, "%.0f", HU_FLAG_SEMI_STATIC, NULL },
|
||||
{ "battery.charge.warning", 0, 0, "UPS.PowerSummary.WarningCapacityLimit", NULL, "%.0f", 0, NULL },
|
||||
{ "battery.runtime", 0, 0, "UPS.PowerSummary.RunTimeToEmpty", NULL, "%.0f", 0, NULL },
|
||||
{ "battery.runtime.low", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.PowerSummary.RemainingTimeLimit", NULL, "%.0f", HU_FLAG_SEMI_STATIC, NULL },
|
||||
{ "battery.voltage", 0, 0, "UPS.PowerSummary.Voltage", NULL, "%.1f", 0, NULL },
|
||||
{ "battery.voltage.nominal", 0, 0, "UPS.Battery.ConfigVoltage", NULL, "%.1f", 0, NULL },
|
||||
{ "battery.voltage.nominal", 0, 0, "UPS.PowerSummary.ConfigVoltage", NULL, "%.1f", 0, NULL }, /* Back-UPS 500 */
|
||||
{ "battery.temperature", 0, 0, "UPS.Battery.Temperature", NULL, "%s", 0, kelvin_celsius_conversion },
|
||||
{ "battery.type", 0, 0, "UPS.PowerSummary.iDeviceChemistry", NULL, "%s", 0, stringid_conversion },
|
||||
{ "battery.mfr.date", 0, 0, "UPS.Battery.ManufacturerDate", NULL, "%s", 0, date_conversion },
|
||||
{ "battery.mfr.date", 0, 0, "UPS.PowerSummary.APCBattReplaceDate", NULL, "%s", 0, apc_date_conversion }, /* Back-UPS 500, Back-UPS ES/CyberFort 500 */
|
||||
{ "battery.date", 0, 0, "UPS.Battery.APCBattReplaceDate", NULL, "%s", 0, apc_date_conversion }, /* Observed values: 0x0 on Back-UPS ES 650, 0x92501 on Back-UPS BF500 whose manufacture date was 2005/01/20 - this makes little sense but at least it's a valid date. */
|
||||
|
||||
/* UPS page */
|
||||
{ "ups.load", 0, 0, "UPS.Output.PercentLoad", NULL, "%.1f", 0, NULL },
|
||||
{ "ups.load", 0, 0, "UPS.PowerConverter.PercentLoad", NULL, "%.0f", 0, NULL },
|
||||
{ "ups.delay.start", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.PowerSummary.DelayBeforeStartup", NULL, DEFAULT_ONDELAY, HU_FLAG_ABSENT, NULL},
|
||||
{ "ups.delay.start", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.APCGeneralCollection.APCDelayBeforeStartup", NULL, DEFAULT_ONDELAY, HU_FLAG_ABSENT, NULL}, /* APC */
|
||||
{ "ups.delay.shutdown", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.PowerSummary.DelayBeforeShutdown", NULL, DEFAULT_OFFDELAY, HU_FLAG_ABSENT, NULL},
|
||||
{ "ups.delay.shutdown", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.APCGeneralCollection.APCDelayBeforeShutdown", NULL, DEFAULT_OFFDELAY, HU_FLAG_ABSENT, NULL}, /* APC */
|
||||
{ "ups.timer.start", 0, 0, "UPS.PowerSummary.DelayBeforeStartup", NULL, "%.0f", HU_FLAG_QUICK_POLL, NULL},
|
||||
{ "ups.timer.start", 0, 0, "UPS.APCGeneralCollection.APCDelayBeforeStartup", NULL, "%.0f", HU_FLAG_QUICK_POLL, NULL}, /* APC */
|
||||
{ "ups.timer.shutdown", 0, 0, "UPS.PowerSummary.DelayBeforeShutdown", NULL, "%.0f", HU_FLAG_QUICK_POLL, NULL},
|
||||
{ "ups.timer.shutdown", 0, 0, "UPS.APCGeneralCollection.APCDelayBeforeShutdown", NULL, "%.0f", HU_FLAG_QUICK_POLL, NULL}, /* APC */
|
||||
{ "ups.timer.reboot", 0, 0, "UPS.PowerSummary.DelayBeforeReboot", NULL, "%.0f", HU_FLAG_QUICK_POLL, NULL},
|
||||
{ "ups.timer.reboot", 0, 0, "UPS.APCGeneralCollection.APCDelayBeforeReboot", NULL, "%.0f", HU_FLAG_QUICK_POLL, NULL}, /* APC */
|
||||
{ "ups.test.result", 0, 0, "UPS.Battery.Test", NULL, "%s", 0, test_read_info },
|
||||
{ "ups.beeper.status", 0, 0, "UPS.PowerSummary.AudibleAlarmControl", NULL, "%s", 0, beeper_info },
|
||||
{ "ups.mfr.date", 0, 0, "UPS.ManufacturerDate", NULL, "%s", 0, date_conversion },
|
||||
{ "ups.mfr.date", 0, 0, "UPS.PowerSummary.ManufacturerDate", NULL, "%s", 0, date_conversion }, /* Back-UPS 500 */
|
||||
|
||||
|
||||
/* the below one need to be discussed as we might need to complete
|
||||
* the ups.test sub collection
|
||||
* { "ups.test.panel", 0, 0, "UPS.APCPanelTest", NULL, "%.0f", 0, NULL }, */
|
||||
|
||||
/* Special case: ups.status & ups.alarm */
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.ACPresent", NULL, NULL, HU_FLAG_QUICK_POLL, online_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.Discharging", NULL, NULL, HU_FLAG_QUICK_POLL, discharging_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.Charging", NULL, NULL, HU_FLAG_QUICK_POLL, charging_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.ShutdownImminent", NULL, NULL, 0, shutdownimm_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.BelowRemainingCapacityLimit", NULL, NULL, HU_FLAG_QUICK_POLL, lowbatt_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.Overload", NULL, NULL, 0, overload_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.NeedReplacement", NULL, NULL, 0, replacebatt_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.RemainingTimeLimitExpired", NULL, NULL, 0, timelimitexpired_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.BatteryPresent", NULL, NULL, 0, nobattery_info },
|
||||
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.Charging", NULL, NULL, HU_FLAG_QUICK_POLL, charging_info }, /* Back-UPS 500 */
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.Discharging", NULL, NULL, HU_FLAG_QUICK_POLL, discharging_info }, /* Back-UPS 500 */
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.ACPresent", NULL, NULL, HU_FLAG_QUICK_POLL, online_info }, /* Back-UPS 500 */
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.BelowRemainingCapacityLimit", NULL, NULL, HU_FLAG_QUICK_POLL, lowbatt_info }, /* Back-UPS 500 */
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.ShutdownImminent", NULL, NULL, 0, shutdownimm_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.APCStatusFlag", NULL, NULL, HU_FLAG_QUICK_POLL, apcstatusflag_info }, /* APC Back-UPS LS 500 */
|
||||
|
||||
/* we map 2 times "input.transfer.reason" to be able to clear
|
||||
* both vrange (voltage) and frange (frequency) */
|
||||
{ "BOOL", 0, 0, "UPS.Input.APCLineFailCause", NULL, NULL, 0, apc_linefailcause_vrange_info },
|
||||
{ "BOOL", 0, 0, "UPS.Input.APCLineFailCause", NULL, NULL, 0, apc_linefailcause_frange_info },
|
||||
|
||||
/* Input page */
|
||||
{ "input.voltage", 0, 0, "UPS.Input.Voltage", NULL, "%.1f", 0, NULL },
|
||||
{ "input.voltage.nominal", 0, 0, "UPS.Input.ConfigVoltage", NULL, "%.0f", 0, NULL },
|
||||
{ "input.transfer.low", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.Input.LowVoltageTransfer", NULL, "%.0f", HU_FLAG_SEMI_STATIC, NULL },
|
||||
{ "input.transfer.high", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.Input.HighVoltageTransfer", NULL, "%.0f", HU_FLAG_SEMI_STATIC, NULL },
|
||||
{ "input.sensitivity", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.Input.APCSensitivity", NULL, "%s", HU_FLAG_SEMI_STATIC, apc_sensitivity_info },
|
||||
|
||||
/* Output page */
|
||||
{ "output.voltage", 0, 0, "UPS.Output.Voltage", NULL, "%.1f", 0, NULL },
|
||||
{ "output.voltage.nominal", 0, 0, "UPS.Output.ConfigVoltage", NULL, "%.1f", 0, NULL },
|
||||
|
||||
/* Environmental page */
|
||||
{ "ambient.temperature", 0, 0, "UPS.APCEnvironment.APCProbe1.Temperature", NULL, "%s", 0, kelvin_celsius_conversion },
|
||||
{ "ambient.humidity", 0, 0, "UPS.APCEnvironment.APCProbe1.Humidity", NULL, "%.1f", 0, NULL },
|
||||
/*
|
||||
{ "ambient.temperature", 0, 0, "UPS.APCEnvironment.APCProbe2.Temperature", NULL, "%.1f", 0, kelvin_celsius_conversion },
|
||||
{ "ambient.humidity", 0, 0, "UPS.APCEnvironment.APCProbe2.Humidity", NULL, "%.1f", 0, NULL },
|
||||
*/
|
||||
|
||||
/* instant commands. */
|
||||
/* test.* split into subset while waiting for extradata support
|
||||
* ie: test.battery.start quick
|
||||
*/
|
||||
{ "test.battery.start.quick", 0, 0, "UPS.BatterySystem.Battery.Test", NULL, "1", HU_TYPE_CMD, NULL },
|
||||
{ "test.battery.start.quick", 0, 0, "UPS.Battery.Test", NULL, "1", HU_TYPE_CMD, NULL }, /* Back-UPS RS (experimental) */
|
||||
{ "test.battery.start.deep", 0, 0, "UPS.BatterySystem.Battery.Test", NULL, "2", HU_TYPE_CMD, NULL },
|
||||
{ "test.battery.start.deep", 0, 0, "UPS.Battery.Test", NULL, "2", HU_TYPE_CMD, NULL }, /* Back-UPS RS (experimental) */
|
||||
{ "test.battery.stop", 0, 0, "UPS.BatterySystem.Battery.Test", NULL, "3", HU_TYPE_CMD, NULL },
|
||||
{ "test.battery.stop", 0, 0, "UPS.Battery.Test", NULL, "3", HU_TYPE_CMD, NULL }, /* Back-UPS RS (experimental) */
|
||||
{ "test.panel.start", 0, 0, "UPS.APCPanelTest", NULL, "1", HU_TYPE_CMD, NULL },
|
||||
{ "test.panel.stop", 0, 0, "UPS.APCPanelTest", NULL, "0", HU_TYPE_CMD, NULL },
|
||||
{ "test.panel.start", 0, 0, "UPS.PowerSummary.APCPanelTest", NULL, "1", HU_TYPE_CMD, NULL }, /* Back-UPS 500 */
|
||||
{ "test.panel.stop", 0, 0, "UPS.PowerSummary.APCPanelTest", NULL, "0", HU_TYPE_CMD, NULL }, /* Back-UPS 500 */
|
||||
|
||||
{ "load.off.delay", 0, 0, "UPS.PowerSummary.DelayBeforeShutdown", NULL, DEFAULT_OFFDELAY, HU_TYPE_CMD, NULL },
|
||||
{ "load.on.delay", 0, 0, "UPS.PowerSummary.DelayBeforeStartup", NULL, DEFAULT_ONDELAY, HU_TYPE_CMD, NULL },
|
||||
{ "shutdown.stop", 0, 0, "UPS.PowerSummary.DelayBeforeShutdown", NULL, "-1", HU_TYPE_CMD, NULL },
|
||||
{ "shutdown.reboot", 0, 0, "UPS.PowerSummary.DelayBeforeReboot", NULL, "10", HU_TYPE_CMD, NULL },
|
||||
/* APC Backups ES */
|
||||
{ "load.off.delay", 0, 0, "UPS.APCGeneralCollection.APCDelayBeforeShutdown", NULL, DEFAULT_OFFDELAY, HU_TYPE_CMD, NULL },
|
||||
{ "load.on.delay", 0, 0, "UPS.APCGeneralCollection.APCDelayBeforeStartup", NULL, DEFAULT_ONDELAY, HU_TYPE_CMD, NULL },
|
||||
{ "shutdown.stop", 0, 0, "UPS.APCGeneralCollection.APCDelayBeforeShutdown", NULL, "-1", HU_TYPE_CMD, NULL },
|
||||
{ "shutdown.reboot", 0, 0, "UPS.APCGeneralCollection.APCDelayBeforeReboot", NULL, "10", HU_TYPE_CMD, NULL },
|
||||
|
||||
{ "beeper.on", 0, 0, "UPS.PowerSummary.AudibleAlarmControl", NULL, "2", HU_TYPE_CMD, NULL },
|
||||
{ "beeper.off", 0, 0, "UPS.PowerSummary.AudibleAlarmControl", NULL, "3", HU_TYPE_CMD, NULL },
|
||||
{ "beeper.enable", 0, 0, "UPS.PowerSummary.AudibleAlarmControl", NULL, "2", HU_TYPE_CMD, NULL },
|
||||
{ "beeper.disable", 0, 0, "UPS.PowerSummary.AudibleAlarmControl", NULL, "1", HU_TYPE_CMD, NULL },
|
||||
{ "beeper.mute", 0, 0, "UPS.PowerSummary.AudibleAlarmControl", NULL, "3", HU_TYPE_CMD, NULL },
|
||||
|
||||
/* end of structure. */
|
||||
{ NULL, 0, 0, NULL, NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
static char *apc_format_model(HIDDevice_t *hd) {
|
||||
static char model[64];
|
||||
char *ptr1, *ptr2;
|
||||
|
||||
/* FIXME?: what is the path "UPS.APC_UPS_FirmwareRevision"? */
|
||||
snprintf(model, sizeof(model), "%s", hd->Product ? hd->Product : "unknown");
|
||||
ptr1 = strstr(model, "FW:");
|
||||
if (ptr1)
|
||||
{
|
||||
*(ptr1 - 1) = '\0';
|
||||
ptr1 += strlen("FW:");
|
||||
ptr2 = strstr(ptr1, "USB FW:");
|
||||
if (ptr2)
|
||||
{
|
||||
*(ptr2 - 1) = '\0';
|
||||
ptr2 += strlen("USB FW:");
|
||||
dstate_setinfo("ups.firmware.aux", "%s", ptr2);
|
||||
}
|
||||
dstate_setinfo("ups.firmware", "%s", ptr1);
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
static char *apc_format_mfr(HIDDevice_t *hd) {
|
||||
return hd->Vendor ? hd->Vendor : "APC";
|
||||
}
|
||||
|
||||
static char *apc_format_serial(HIDDevice_t *hd) {
|
||||
return hd->Serial;
|
||||
}
|
||||
|
||||
/* this function allows the subdriver to "claim" a device: return 1 if
|
||||
* the device is supported by this subdriver, else 0. */
|
||||
static int apc_claim(HIDDevice_t *hd) {
|
||||
|
||||
int status = is_usb_device_supported(apc_usb_device_table, hd->VendorID,
|
||||
hd->ProductID);
|
||||
|
||||
switch (status) {
|
||||
|
||||
case POSSIBLY_SUPPORTED:
|
||||
/* by default, reject, unless the productid option is given */
|
||||
if (getval("productid")) {
|
||||
return 1;
|
||||
}
|
||||
possibly_supported("APC", hd);
|
||||
return 0;
|
||||
|
||||
case SUPPORTED:
|
||||
return 1;
|
||||
|
||||
case NOT_SUPPORTED:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
subdriver_t apc_subdriver = {
|
||||
APC_HID_VERSION,
|
||||
apc_claim,
|
||||
apc_utab,
|
||||
apc_hid2nut,
|
||||
apc_format_model,
|
||||
apc_format_mfr,
|
||||
apc_format_serial,
|
||||
};
|
||||
34
drivers/apc-hid.h
Normal file
34
drivers/apc-hid.h
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/* apc-hid.h - data to monitor APC USB/HID devices with NUT
|
||||
*
|
||||
* Copyright (C)
|
||||
* 2003 - 2005 Arnaud Quette <arnaud.quette@free.fr>
|
||||
* 2005 John Stamp <kinsayder@hotmail.com>
|
||||
* 2005 Peter Selinger <selinger@users.sourceforge.net>
|
||||
*
|
||||
* Sponsored by MGE UPS SYSTEMS <http://www.mgeups.com>
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef APC_HID_H
|
||||
#define APC_HID_H
|
||||
|
||||
#include "usbhid-ups.h"
|
||||
|
||||
extern subdriver_t apc_subdriver;
|
||||
|
||||
#endif /* APC_HID_H */
|
||||
245
drivers/apc-mib.c
Normal file
245
drivers/apc-mib.c
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
/* apc-mib.c - data to monitor APC SNMP devices (Powernet MIB) with NUT
|
||||
*
|
||||
* Copyright (C) 2002-2003
|
||||
* Dmitry Frolov <frolov@riss-telecom.ru>
|
||||
* Arnaud Quette <arnaud.quette@free.fr>
|
||||
*
|
||||
* Sponsored by MGE UPS SYSTEMS <http://www.mgeups.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "apc-mib.h"
|
||||
|
||||
#define APCC_MIB_VERSION "1.1"
|
||||
|
||||
/* info elements */
|
||||
|
||||
#define APCC_OID_BATT_STATUS ".1.3.6.1.4.1.318.1.1.1.2.1.1.0"
|
||||
/* Defines for APCC_OID_BATT_STATUS */
|
||||
static info_lkp_t apcc_batt_info[] = {
|
||||
{ 1, "" }, /* unknown */
|
||||
{ 2, "" }, /* batteryNormal */
|
||||
{ 3, "LB" }, /* batteryLow */
|
||||
{ 0, "NULL" }
|
||||
} ;
|
||||
|
||||
#define APCC_OID_POWER_STATUS ".1.3.6.1.4.1.318.1.1.1.4.1.1.0"
|
||||
/* Defines for APCC_OID_POWER_STATUS */
|
||||
static info_lkp_t apcc_pwr_info[] = {
|
||||
{ 1, "" }, /* unknown */
|
||||
{ 2, "OL" }, /* onLine */
|
||||
{ 3, "OB" }, /* onBattery */
|
||||
{ 4, "BOOST" }, /* onSmartBoost */
|
||||
{ 5, "OFF" }, /* timedSleeping */
|
||||
{ 6, "OFF" }, /* softwareBypass */
|
||||
{ 7, "OFF" }, /* off */
|
||||
{ 8, "" }, /* rebooting */
|
||||
{ 9, "BYPASS" }, /* switchedBypass */
|
||||
{ 10, "BYPASS" }, /* hardwareFailureBypass */
|
||||
{ 11, "OFF" }, /* sleepingUntilPowerReturn */
|
||||
{ 12, "TRIM" }, /* onSmartTrim */
|
||||
{ 0, "NULL" }
|
||||
} ;
|
||||
|
||||
#define APCC_OID_CAL_RESULTS ".1.3.6.1.4.1.318.1.1.1.7.2.6.0"
|
||||
static info_lkp_t apcc_cal_info[] = {
|
||||
{ 1, "" }, /* Calibration Successful */
|
||||
{ 2, "" }, /* Calibration not done, battery capacity below 100% */
|
||||
{ 3, "CAL" }, /* Calibration in progress */
|
||||
{ 0, "NULL" }
|
||||
};
|
||||
|
||||
#define APCC_OID_NEEDREPLBATT ".1.3.6.1.4.1.318.1.1.1.2.2.4.0"
|
||||
static info_lkp_t apcc_battrepl_info[] = {
|
||||
{ 1, "" }, /* No battery needs replacing */
|
||||
{ 2, "RB" }, /* Batteries need to be replaced */
|
||||
{ 0, "NULL" }
|
||||
};
|
||||
|
||||
#define APCC_OID_TESTDIAGRESULTS ".1.3.6.1.4.1.318.1.1.1.7.2.3.0"
|
||||
static info_lkp_t apcc_testdiag_results[] = {
|
||||
{ 1, "Ok" },
|
||||
{ 2, "Failed" },
|
||||
{ 3, "InvalidTest" },
|
||||
{ 4, "TestInProgress"},
|
||||
{ 0, "NULL" }
|
||||
};
|
||||
|
||||
#define APCC_OID_SENSITIVITY ".1.3.6.1.4.1.318.1.1.1.5.2.7.0"
|
||||
static info_lkp_t apcc_sensitivity_modes[] = {
|
||||
{ 1, "auto" },
|
||||
{ 2, "low" },
|
||||
{ 3, "medium" },
|
||||
{ 4, "high" },
|
||||
{ 0, "NULL" }
|
||||
};
|
||||
|
||||
|
||||
/* --- */
|
||||
|
||||
/* commands */
|
||||
#define APCC_OID_REBOOT ".1.3.6.1.4.1.318.1.1.1.6.2.2"
|
||||
#define APCC_REBOOT_DO 2
|
||||
#define APCC_REBOOT_GRACEFUL 3
|
||||
#if 0 /* not used. */
|
||||
#define APCC_OID_SLEEP ".1.3.6.1.4.1.318.1.1.1.6.2.3"
|
||||
#define APCC_SLEEP_ON "2"
|
||||
#define APCC_SLEEP_GRACEFUL "3"
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
static snmp_info_t apcc_mib[] = {
|
||||
|
||||
/* info elements. */
|
||||
{ "ups.mfr", ST_FLAG_STRING, SU_INFOSIZE, NULL, "APC",
|
||||
SU_FLAG_STATIC | SU_FLAG_ABSENT | SU_FLAG_OK, NULL },
|
||||
{ "ups.model", ST_FLAG_STRING, SU_INFOSIZE, ".1.3.6.1.4.1.318.1.1.1.1.1.1.0", "Generic Powernet SNMP device", SU_FLAG_STATIC | SU_FLAG_OK, NULL },
|
||||
{ "ups.serial", ST_FLAG_STRING, SU_INFOSIZE, ".1.3.6.1.4.1.318.1.1.1.1.2.3.0", "", SU_FLAG_STATIC | SU_FLAG_OK, NULL },
|
||||
{ "ups.mfr.date", ST_FLAG_STRING, SU_INFOSIZE, ".1.3.6.1.4.1.318.1.1.1.1.2.2.0", "", SU_FLAG_OK | SU_FLAG_STATIC, NULL },
|
||||
{ "input.voltage", 0, 1, ".1.3.6.1.4.1.318.1.1.1.3.2.1.0", "", SU_FLAG_OK, NULL },
|
||||
{ "input.phases", ST_FLAG_STRING, 2, ".1.3.6.1.4.1.318.1.1.1.9.2.2.1.2.1", "", SU_FLAG_STATIC | SU_FLAG_OK, NULL },
|
||||
{ "input.L1-L2.voltage", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.2.3.1.3.1.1.1", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "input.L2-L3.voltage", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.2.3.1.3.1.1.2", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "input.L3-L1.voltage", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.2.3.1.3.1.1.3", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "input.L1-L2.voltage.maximum", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.2.3.1.4.1.1.1", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "input.L2-L3.voltage.maximum", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.2.3.1.4.1.1.2", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "input.L3-L1.voltage.maximum", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.2.3.1.4.1.1.3", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "input.L1-L2.voltage.minimum", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.2.3.1.5.1.1.1", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "input.L2-L3.voltage.minimum", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.2.3.1.5.1.1.2", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "input.L3-L1.voltage.minimum", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.2.3.1.5.1.1.3", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "input.L1.current", 0, 0.1, ".1.3.6.1.4.1.318.1.1.1.9.2.3.1.6.1.1.1", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "input.L2.current", 0, 0.1, ".1.3.6.1.4.1.318.1.1.1.9.2.3.1.6.1.1.2", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "input.L3.current", 0, 0.1, ".1.3.6.1.4.1.318.1.1.1.9.2.3.1.6.1.1.3", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "input.L1.current.maximum", 0, 0.1, ".1.3.6.1.4.1.318.1.1.1.9.2.3.1.7.1.1.1", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "input.L2.current.maximum", 0, 0.1, ".1.3.6.1.4.1.318.1.1.1.9.2.3.1.7.1.1.2", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "input.L3.current.maximum", 0, 0.1, ".1.3.6.1.4.1.318.1.1.1.9.2.3.1.7.1.1.3", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "input.L1.current.minimum", 0, 0.1, ".1.3.6.1.4.1.318.1.1.1.9.2.3.1.8.1.1.1", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "input.L2.current.minimum", 0, 0.1, ".1.3.6.1.4.1.318.1.1.1.9.2.3.1.8.1.1.2", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "input.L3.current.minimum", 0, 0.1, ".1.3.6.1.4.1.318.1.1.1.9.2.3.1.8.1.1.3", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "input.frequency", 0, 0.1, ".1.3.6.1.4.1.318.1.1.1.9.2.2.1.4.1", "", SU_FLAG_OK|SU_FLAG_NEGINVALID|SU_FLAG_UNIQUE, NULL },
|
||||
{ "input.frequency", 0, 1, ".1.3.6.1.4.1.318.1.1.1.3.2.4.0", "", SU_FLAG_OK, NULL },
|
||||
{ "input.transfer.low", ST_FLAG_STRING | ST_FLAG_RW, 3, ".1.3.6.1.4.1.318.1.1.1.5.2.3.0", "", SU_TYPE_INT | SU_FLAG_OK, NULL },
|
||||
{ "input.transfer.high", ST_FLAG_STRING | ST_FLAG_RW, 3, ".1.3.6.1.4.1.318.1.1.1.5.2.2.0", "", SU_TYPE_INT | SU_FLAG_OK, NULL },
|
||||
{ "input.sensitivity", ST_FLAG_STRING | ST_FLAG_RW, 1, APCC_OID_SENSITIVITY, "", SU_TYPE_INT | SU_FLAG_OK, apcc_sensitivity_modes },
|
||||
{ "ups.status", ST_FLAG_STRING, SU_INFOSIZE, APCC_OID_POWER_STATUS, "OFF",
|
||||
SU_FLAG_OK | SU_STATUS_PWR, apcc_pwr_info },
|
||||
{ "ups.status", ST_FLAG_STRING, SU_INFOSIZE, APCC_OID_BATT_STATUS, "",
|
||||
SU_FLAG_OK | SU_STATUS_BATT, apcc_batt_info },
|
||||
{ "ups.status", ST_FLAG_STRING, SU_INFOSIZE, APCC_OID_CAL_RESULTS, "",
|
||||
SU_FLAG_OK | SU_STATUS_CAL, apcc_cal_info },
|
||||
{ "ups.status", ST_FLAG_STRING, SU_INFOSIZE, APCC_OID_NEEDREPLBATT, "",
|
||||
SU_FLAG_OK | SU_STATUS_RB, apcc_battrepl_info },
|
||||
{ "ups.temperature", 0, 1, ".1.3.6.1.4.1.318.1.1.1.2.2.2.0", "", SU_FLAG_OK, NULL },
|
||||
{ "ups.load", 0, 1, ".1.3.6.1.4.1.318.1.1.1.4.2.3.0", "", SU_FLAG_OK, NULL },
|
||||
{ "ups.firmware", ST_FLAG_STRING, 16, ".1.3.6.1.4.1.318.1.1.1.1.2.1.0", "", SU_FLAG_STATIC | SU_FLAG_OK, NULL },
|
||||
{ "ups.delay.shutdown", ST_FLAG_STRING | ST_FLAG_RW, 3, ".1.3.6.1.4.1.318.1.1.1.5.2.10.0", "", SU_FLAG_OK, NULL },
|
||||
{ "ups.delay.start", ST_FLAG_STRING | ST_FLAG_RW, 3, ".1.3.6.1.4.1.318.1.1.1.5.2.9.0", "", SU_FLAG_OK, NULL },
|
||||
{ "battery.charge", 0, 1, ".1.3.6.1.4.1.318.1.1.1.2.2.1.0", "", SU_FLAG_OK, NULL },
|
||||
{ "battery.charge.restart", ST_FLAG_STRING | ST_FLAG_RW, 3, ".1.3.6.1.4.1.318.1.1.1.5.2.6.0", "", SU_TYPE_INT | SU_FLAG_OK, NULL },
|
||||
{ "battery.runtime", 0, 1, ".1.3.6.1.4.1.318.1.1.1.2.2.3.0", "", SU_FLAG_OK, NULL },
|
||||
{ "battery.runtime.low", ST_FLAG_STRING | ST_FLAG_RW, 3, ".1.3.6.1.4.1.318.1.1.1.5.2.8.0", "", SU_FLAG_OK, NULL },
|
||||
{ "battery.voltage", 0, 1, ".1.3.6.1.4.1.318.1.1.1.2.2.8.0", "", SU_FLAG_OK, NULL },
|
||||
{ "battery.voltage.nominal", 0, 1, ".1.3.6.1.4.1.318.1.1.1.2.2.7.0", "", SU_FLAG_OK, NULL },
|
||||
{ "battery.current", 0, 1, ".1.3.6.1.4.1.318.1.1.1.2.2.9.0", "", SU_FLAG_OK, NULL },
|
||||
{ "battery.packs", 0, 1, ".1.3.6.1.4.1.318.1.1.1.2.2.5.0", "", SU_FLAG_OK, NULL },
|
||||
{ "battery.packs.bad", 0, 1, ".1.3.6.1.4.1.318.1.1.1.2.2.6.0", "", SU_FLAG_OK, NULL },
|
||||
{ "battery.date", ST_FLAG_STRING | ST_FLAG_RW, 8, ".1.3.6.1.4.1.318.1.1.1.2.1.3.0", "", SU_FLAG_OK | SU_FLAG_STATIC | SU_TYPE_STRING, NULL },
|
||||
{ "ups.id", ST_FLAG_STRING | ST_FLAG_RW, 8, ".1.3.6.1.4.1.318.1.1.1.1.1.2.0", "", SU_FLAG_OK | SU_FLAG_STATIC | SU_TYPE_STRING, NULL },
|
||||
{ "ups.test.result", ST_FLAG_STRING, SU_INFOSIZE, APCC_OID_TESTDIAGRESULTS, "", SU_FLAG_OK, apcc_testdiag_results },
|
||||
{ "output.voltage", 0, 1, ".1.3.6.1.4.1.318.1.1.1.4.2.1.0", "", SU_FLAG_OK, NULL },
|
||||
{ "output.phases", ST_FLAG_STRING, 2, ".1.3.6.1.4.1.318.1.1.1.9.3.2.1.2.1", "", SU_FLAG_STATIC | SU_FLAG_OK, NULL },
|
||||
{ "output.frequency", 0, 0.1, ".1.3.6.1.4.1.318.1.1.1.9.3.2.1.4.1", "", SU_FLAG_OK|SU_FLAG_NEGINVALID|SU_FLAG_UNIQUE, NULL },
|
||||
{ "output.frequency", 0, 1, ".1.3.6.1.4.1.318.1.1.1.4.2.2.0", "", SU_FLAG_OK, NULL },
|
||||
{ "output.current", 0, 1, ".1.3.6.1.4.1.318.1.1.1.4.2.4.0", "", SU_FLAG_OK, NULL },
|
||||
{ "output.L1-L2.voltage", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.3.1.1.1", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L2-L3.voltage", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.3.1.1.2", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L3-L1.voltage", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.3.1.1.3", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L1.current", 0, 0.1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.4.1.1.1", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L2.current", 0, 0.1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.4.1.1.2", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L3.current", 0, 0.1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.4.1.1.3", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L1.current.maximum", 0, 0.1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.5.1.1.1", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L2.current.maximum", 0, 0.1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.5.1.1.2", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L3.current.maximum", 0, 0.1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.5.1.1.3", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L1.current.minimum", 0, 0.1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.6.1.1.1", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L2.current.minimum", 0, 0.1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.6.1.1.2", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L3.current.minimum", 0, 0.1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.6.1.1.3", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L1.power", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.7.1.1.1", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L2.power", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.7.1.1.2", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L3.power", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.7.1.1.3", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L1.power.maximum", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.8.1.1.1", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L2.power.maximum", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.8.1.1.2", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L3.power.maximum", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.8.1.1.3", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L1.power.minimum", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.9.1.1.1", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L2.power.minimum", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.9.1.1.2", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L3.power.minimum", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.9.1.1.3", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L1.power.percent", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.10.1.1.1", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L2.power.percent", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.10.1.1.2", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L3.power.percent", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.10.1.1.3", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L1.power.maximum.percent", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.11.1.1.1", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L2.power.maximum.percent", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.11.1.1.2", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L3.power.maximum.percent", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.11.1.1.3", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L1.power.minimum.percent", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.12.1.1.1", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L2.power.minimum.percent", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.12.1.1.2", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.L3.power.minimum.percent", 0, 1, ".1.3.6.1.4.1.318.1.1.1.9.3.3.1.12.1.1.3", "", SU_FLAG_OK|SU_FLAG_NEGINVALID, NULL },
|
||||
{ "output.voltage.nominal", ST_FLAG_STRING | ST_FLAG_RW, 3, ".1.3.6.1.4.1.318.1.1.1.5.2.1.0", "", SU_TYPE_INT | SU_FLAG_OK, NULL },
|
||||
|
||||
/* Measure-UPS ambient variables */
|
||||
/* Environmental sensors (AP9612TH and others) */
|
||||
{ "ambient.temperature", 0, 1, ".1.3.6.1.4.1.318.1.1.2.1.1.0", "", SU_FLAG_OK, NULL },
|
||||
{ "ambient.temperature.high", 0, 1, ".1.3.6.1.4.1.318.1.1.10.1.2.2.1.3.1", "", SU_FLAG_OK, NULL },
|
||||
{ "ambient.temperature.low", 0, 1, ".1.3.6.1.4.1.318.1.1.10.1.2.2.1.4.1", "", SU_FLAG_OK, NULL },
|
||||
{ "ambient.humidity", 0, 1, ".1.3.6.1.4.1.318.1.1.2.1.2.0", "", SU_FLAG_OK, NULL },
|
||||
{ "ambient.humidity.high", 0, 1, ".1.3.6.1.4.1.318.1.1.10.1.2.2.1.6.1", "", SU_FLAG_OK, NULL },
|
||||
{ "ambient.humidity.low", 0, 1, ".1.3.6.1.4.1.318.1.1.10.1.2.2.1.7.1", "", SU_FLAG_OK, NULL },
|
||||
|
||||
/* IEM ambient variables */
|
||||
/* IEM: integrated environment monitor probe */
|
||||
#define APCC_OID_IEM_TEMP ".1.3.6.1.4.1.318.1.1.10.2.3.2.1.4.1"
|
||||
#define APCC_OID_IEM_TEMP_UNIT ".1.3.6.1.4.1.318.1.1.10.2.3.2.1.5.1"
|
||||
#define APCC_IEM_FAHRENHEIT 2
|
||||
#define APCC_OID_IEM_HUMID ".1.3.6.1.4.1.318.1.1.10.2.3.2.1.6.1"
|
||||
{ "ambient.temperature", 0, 1, APCC_OID_IEM_TEMP, "", SU_FLAG_OK, NULL },
|
||||
{ "ambient.humidity", 0, 1, APCC_OID_IEM_HUMID, "", SU_FLAG_OK, NULL },
|
||||
|
||||
/* instant commands. */
|
||||
{ "load.off", 0, 2, ".1.3.6.1.4.1.318.1.1.1.6.2.1.0", "", SU_TYPE_CMD | SU_FLAG_OK, NULL },
|
||||
{ "load.on", 0, 2, ".1.3.6.1.4.1.318.1.1.1.6.2.6.0", "", SU_TYPE_CMD | SU_FLAG_OK, NULL },
|
||||
{ "shutdown.stayoff", 0, 3, ".1.3.6.1.4.1.318.1.1.1.6.2.1.0", "", SU_TYPE_CMD | SU_FLAG_OK, NULL },
|
||||
|
||||
/* { CMD_SDRET, 0, APCC_REBOOT_GRACEFUL, APCC_OID_REBOOT, "", SU_TYPE_CMD | SU_FLAG_OK, NULL }, */
|
||||
|
||||
{ "shutdown.return", 0, 2, ".1.3.6.1.4.1.318.1.1.1.6.1.1.0", "", SU_TYPE_CMD | SU_FLAG_OK, NULL },
|
||||
{ "test.failure.start", 0, 2, ".1.3.6.1.4.1.318.1.1.1.6.2.4.0", "", SU_TYPE_CMD | SU_FLAG_OK, NULL },
|
||||
{ "test.panel.start", 0, 2, ".1.3.6.1.4.1.318.1.1.1.6.2.5.0", "", SU_TYPE_CMD | SU_FLAG_OK, NULL },
|
||||
{ "bypass.start", 0, 2, ".1.3.6.1.4.1.318.1.1.1.6.2.7.0", "", SU_TYPE_CMD | SU_FLAG_OK, NULL },
|
||||
{ "bypass.stop", 0, 3, ".1.3.6.1.4.1.318.1.1.1.6.2.7.0", "", SU_TYPE_CMD | SU_FLAG_OK, NULL },
|
||||
{ "test.battery.start", 0, 2, ".1.3.6.1.4.1.318.1.1.1.7.2.2.0", "", SU_TYPE_CMD | SU_FLAG_OK, NULL },
|
||||
{ "calibrate.start", 0, 2, ".1.3.6.1.4.1.318.1.1.1.7.2.5.0", "", SU_TYPE_CMD | SU_FLAG_OK, NULL },
|
||||
{ "calibrate.stop", 0, 3, ".1.3.6.1.4.1.318.1.1.1.7.2.5.0", "", SU_TYPE_CMD | SU_FLAG_OK, NULL },
|
||||
{ "reset.input.minmax", 0, 2, ".1.3.6.1.4.1.318.1.1.1.9.1.1.0", "", SU_TYPE_CMD | SU_FLAG_OK, NULL },
|
||||
|
||||
/* end of structure. */
|
||||
{ NULL, 0, 0, NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
mib2nut_info_t apc = { "apcc", APCC_MIB_VERSION, APCC_OID_POWER_STATUS, ".1.3.6.1.4.1.318.1.1.1.1.1.1.0", apcc_mib };
|
||||
|
||||
/*
|
||||
vim:ts=4:sw=4:et:
|
||||
*/
|
||||
18
drivers/apc-mib.h
Normal file
18
drivers/apc-mib.h
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#ifndef APC_MIB_H
|
||||
#define APC_MIB_H
|
||||
|
||||
#include "main.h"
|
||||
#include "snmp-ups.h"
|
||||
|
||||
/*
|
||||
* FIXME: The below is needed because the main driver body uses this to determine
|
||||
* whether a conversion from Fahrenheit to Celsius is needed (which really should
|
||||
* be solved in subdriver specific formatting functions, like we do in usbhid-ups
|
||||
*/
|
||||
#define APCC_OID_IEM_TEMP ".1.3.6.1.4.1.318.1.1.10.2.3.2.1.4.1"
|
||||
#define APCC_OID_IEM_TEMP_UNIT ".1.3.6.1.4.1.318.1.1.10.2.3.2.1.5.1"
|
||||
#define APCC_IEM_FAHRENHEIT 2
|
||||
|
||||
extern mib2nut_info_t apc;
|
||||
|
||||
#endif /* APC_MIB_H */
|
||||
1313
drivers/apcsmart.c
Normal file
1313
drivers/apcsmart.c
Normal file
File diff suppressed because it is too large
Load diff
285
drivers/apcsmart.h
Normal file
285
drivers/apcsmart.h
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
/* apcsmart.h - command table for APC smart protocol units
|
||||
|
||||
Copyright (C) 1999 Russell Kroll <rkroll@exploits.org>
|
||||
(C) 2000 Nigel Metheringham <Nigel.Metheringham@Intechnology.co.uk>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include "serial.h"
|
||||
#include "timehead.h"
|
||||
|
||||
#define APC_TABLE_VERSION "version 2.1"
|
||||
|
||||
/* Basic UPS reply line structure */
|
||||
#define ENDCHAR 10 /* APC ends responses with LF */
|
||||
|
||||
/* these two are only used during startup */
|
||||
#define IGNCHARS "\015+$|!~%?=*#&" /* special characters to ignore */
|
||||
#define MINIGNCHARS "\015+$|!" /* minimum set of special characters to ignore */
|
||||
|
||||
/* normal polls: characters we don't want to parse (including a few alerts) */
|
||||
#define POLL_IGNORE "\015?=*&|"
|
||||
|
||||
/* alert characters we care about - OL, OB, LB, not LB, RB */
|
||||
#define POLL_ALERT "$!%+#"
|
||||
|
||||
#define UPSDELAY 50000 /* slow down multicharacter commands */
|
||||
#define CMDLONGDELAY 1500000 /* some commands need a 1.5s gap for safety */
|
||||
|
||||
#define SER_WAIT_SEC 3 /* wait up to 3.0 sec for ser_get calls */
|
||||
#define SER_WAIT_USEC 0
|
||||
|
||||
/* dangerous instant commands must be reconfirmed within a 12 second window */
|
||||
#define MINCMDTIME 3
|
||||
#define MAXCMDTIME 15
|
||||
|
||||
/* it only does two strings, and they're both the same length */
|
||||
#define APC_STRLEN 8
|
||||
|
||||
/* --------------- */
|
||||
|
||||
/* status bits */
|
||||
|
||||
#define APC_STAT_CAL 1 /* calibration */
|
||||
#define APC_STAT_TRIM 2 /* SmartTrim */
|
||||
#define APC_STAT_BOOST 4 /* SmartBoost */
|
||||
#define APC_STAT_OL 8 /* on line */
|
||||
#define APC_STAT_OB 16 /* on battery */
|
||||
#define APC_STAT_OVER 32 /* overload */
|
||||
#define APC_STAT_LB 64 /* low battery */
|
||||
#define APC_STAT_RB 128 /* replace battery */
|
||||
|
||||
/* serial protocol: special commands - initialization and such */
|
||||
#define APC_STATUS 'Q'
|
||||
#define APC_GOSMART 'Y'
|
||||
#define APC_GODUMB 'R'
|
||||
#define APC_CMDSET 'a'
|
||||
#define APC_CAPABILITY 26 /* ^Z */
|
||||
#define APC_NEXTVAL '-'
|
||||
|
||||
/* --------------- */
|
||||
|
||||
/* Driver command table flag values */
|
||||
|
||||
#define APC_POLL 0x0001 /* Poll this variable regularly */
|
||||
#define APC_IGNORE 0x0002 /* Never poll this */
|
||||
#define APC_PRESENT 0x0004 /* Capability seen on this UPS */
|
||||
|
||||
#define APC_RW 0x0010 /* read-write variable */
|
||||
#define APC_ENUM 0x0020 /* enumerated type */
|
||||
#define APC_STRING 0x0040 /* string */
|
||||
|
||||
#define APC_NASTY 0x0100 /* Nasty command - take care */
|
||||
#define APC_REPEAT 0x0200 /* Command needs sending twice */
|
||||
|
||||
#define APC_FORMATMASK 0xFF0000 /* Mask for apc data formats */
|
||||
|
||||
#define APC_F_PERCENT 0x020000 /* Data in a percent format */
|
||||
#define APC_F_VOLT 0x030000 /* Data in a voltage format */
|
||||
#define APC_F_AMP 0x040000 /* Data in a current/amp format */
|
||||
#define APC_F_CELSIUS 0x050000 /* Data in a temp/C format */
|
||||
#define APC_F_HEX 0x060000 /* Data in a hex number format */
|
||||
#define APC_F_DEC 0x070000 /* Data in a decimal format */
|
||||
#define APC_F_SECONDS 0x100000 /* Time in seconds */
|
||||
#define APC_F_MINUTES 0x110000 /* Time in minutes */
|
||||
#define APC_F_HOURS 0x120000 /* Time in hours */
|
||||
#define APC_F_REASON 0x130000 /* Reason of transfer */
|
||||
#define APC_F_LEAVE 0 /* Just pass this through */
|
||||
|
||||
struct apc_vartab_t {
|
||||
const char *name; /* the variable name */
|
||||
unsigned int flags; /* various flags */
|
||||
char cmd; /* command character */
|
||||
|
||||
} apc_vartab[] = {
|
||||
|
||||
{ "ups.firmware", 0, 'b' },
|
||||
{ "ups.firmware.aux", 0, 'v' },
|
||||
{ "ups.model", 0, 0x01 },
|
||||
|
||||
/* FUTURE: depends on variable naming scheme */
|
||||
#if 0
|
||||
{ "ups.model.code", 0, 'V' },
|
||||
#endif
|
||||
|
||||
{ "ups.serial", 0, 'n' },
|
||||
{ "ups.mfr.date", 0, 'm' },
|
||||
|
||||
{ "ups.temperature", APC_POLL|APC_F_CELSIUS, 'C' },
|
||||
{ "ups.load", APC_POLL|APC_F_PERCENT, 'P' },
|
||||
|
||||
{ "ups.test.interval", APC_F_HOURS, 'E' },
|
||||
{ "ups.test.result", APC_POLL, 'X' },
|
||||
|
||||
{ "ups.delay.start", APC_F_SECONDS, 'r' },
|
||||
{ "ups.delay.shutdown", APC_F_SECONDS, 'p' },
|
||||
|
||||
{ "ups.id", APC_STRING, 'c' },
|
||||
|
||||
{ "ups.contacts", APC_POLL|APC_F_HEX, 'i' },
|
||||
{ "ups.display.language",
|
||||
0, 0x0C },
|
||||
|
||||
{ "input.voltage", APC_POLL|APC_F_VOLT, 'L' },
|
||||
{ "input.frequency", APC_POLL|APC_F_DEC, 'F' },
|
||||
{ "input.sensitivity", 0, 's' },
|
||||
{ "input.quality", APC_POLL|APC_F_HEX, '9' },
|
||||
|
||||
{ "input.transfer.low", APC_F_VOLT, 'l' },
|
||||
{ "input.transfer.high",
|
||||
APC_F_VOLT, 'u' },
|
||||
{ "input.transfer.reason",
|
||||
APC_POLL|APC_F_REASON, 'G' },
|
||||
|
||||
{ "input.voltage.maximum",
|
||||
APC_POLL|APC_F_VOLT, 'M' },
|
||||
{ "input.voltage.minimum",
|
||||
APC_POLL|APC_F_VOLT, 'N' },
|
||||
|
||||
{ "output.current", APC_POLL|APC_F_AMP, '/' },
|
||||
{ "output.voltage", APC_POLL|APC_F_VOLT, 'O' },
|
||||
{ "output.voltage.nominal",
|
||||
APC_F_VOLT, 'o' },
|
||||
|
||||
{ "ambient.humidity", APC_POLL|APC_F_PERCENT, 'h' },
|
||||
{ "ambient.humidity.high",
|
||||
APC_F_PERCENT, '{' },
|
||||
{ "ambient.humidity.low",
|
||||
APC_F_PERCENT, '}' },
|
||||
|
||||
{ "ambient.temperature",
|
||||
APC_POLL|APC_F_CELSIUS, 't' },
|
||||
{ "ambient.temperature.high",
|
||||
APC_F_CELSIUS, '[' },
|
||||
{ "ambient.temperature.low",
|
||||
APC_F_CELSIUS, ']' },
|
||||
|
||||
{ "battery.date", APC_STRING, 'x' },
|
||||
|
||||
{ "battery.charge", APC_POLL|APC_F_PERCENT, 'f' },
|
||||
{ "battery.charge.restart",
|
||||
APC_F_PERCENT, 'e' },
|
||||
|
||||
{ "battery.voltage", APC_POLL|APC_F_VOLT, 'B' },
|
||||
{ "battery.voltage.nominal",
|
||||
0, 'g' },
|
||||
|
||||
{ "battery.runtime", APC_POLL|APC_F_MINUTES, 'j' },
|
||||
{ "battery.runtime.low",
|
||||
APC_F_MINUTES, 'q' },
|
||||
|
||||
{ "battery.packs", APC_F_DEC, '>' },
|
||||
{ "battery.packs.bad", APC_F_DEC, '<' },
|
||||
{ "battery.alarm.threshold",
|
||||
0, 'k' },
|
||||
/* todo:
|
||||
|
||||
I = alarm enable (hex field) - split into alarm.n.enable
|
||||
J = alarm status (hex field) - split into alarm.n.status
|
||||
|
||||
0x15 = output voltage selection (APC_F_VOLT)
|
||||
0x5C = load power (APC_POLL|APC_F_PERCENT)
|
||||
|
||||
*/
|
||||
|
||||
{NULL, 0, 0},
|
||||
};
|
||||
|
||||
/* ------ instant commands ------ */
|
||||
|
||||
#define APC_CMD_FPTEST 'A'
|
||||
#define APC_CMD_CALTOGGLE 'D'
|
||||
#define APC_CMD_SHUTDOWN 'K'
|
||||
#define APC_CMD_SOFTDOWN 'S'
|
||||
#define APC_CMD_SIMPWF 'U'
|
||||
#define APC_CMD_BTESTTOGGLE 'W'
|
||||
#define APC_CMD_OFF 'Z'
|
||||
|
||||
#define APC_CMD_ON 0x0E /* ^N */
|
||||
#define APC_CMD_BYPTOGGLE '^'
|
||||
|
||||
struct apc_cmdtab_t {
|
||||
const char *name;
|
||||
int flags;
|
||||
char cmd;
|
||||
} apc_cmdtab[] =
|
||||
{
|
||||
{ "load.off", APC_NASTY|APC_REPEAT, APC_CMD_OFF },
|
||||
{ "load.on", APC_REPEAT, APC_CMD_ON },
|
||||
|
||||
{ "test.panel.start", 0, APC_CMD_FPTEST },
|
||||
|
||||
{ "test.failure.start", 0, APC_CMD_SIMPWF },
|
||||
|
||||
{ "test.battery.start", 0, APC_CMD_BTESTTOGGLE },
|
||||
{ "test.battery.stop", 0, APC_CMD_BTESTTOGGLE },
|
||||
|
||||
{ "shutdown.return", APC_NASTY, APC_CMD_SOFTDOWN },
|
||||
{ "shutdown.stayoff", APC_NASTY|APC_REPEAT, APC_CMD_SHUTDOWN },
|
||||
|
||||
{ "calibrate.start", 0, APC_CMD_CALTOGGLE },
|
||||
{ "calibrate.stop", 0, APC_CMD_CALTOGGLE },
|
||||
|
||||
{ "bypass.start", 0, APC_CMD_BYPTOGGLE },
|
||||
{ "bypass.stop", 0, APC_CMD_BYPTOGGLE },
|
||||
|
||||
{ NULL, 0, 0 }
|
||||
};
|
||||
|
||||
/* things to ignore in protocol_verify - useless variables, etc. */
|
||||
#define CMD_IGN_CHARS "\032-78@.,~\047\177QHRTYayz)1IJ"
|
||||
|
||||
/* compatibility with hardware that doesn't do APC_CMDSET ('a') */
|
||||
|
||||
struct {
|
||||
const char *firmware;
|
||||
const char *cmdchars;
|
||||
int flags;
|
||||
} compat_tab[] =
|
||||
{
|
||||
/* APC Matrix */
|
||||
{ "0ZI", "79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz/<>", 0 },
|
||||
{ "5UI", "79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz/<>", 0 },
|
||||
{ "5ZM", "79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz/<>", 0 },
|
||||
/* APC600 */
|
||||
{ "6QD", "79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
|
||||
{ "6QI", "79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
|
||||
{ "6TD", "79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
|
||||
{ "6TI", "79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
|
||||
/* SmartUPS 900 */
|
||||
{ "7QD", "79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
|
||||
{ "7QI", "79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
|
||||
{ "7TD", "79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
|
||||
{ "7TI", "79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
|
||||
/* SmartUPS 1250. */
|
||||
{ "8QD", "79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
|
||||
{ "8QI", "79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
|
||||
{ "8TD", "79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
|
||||
{ "8TI", "79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
|
||||
/* CS 350 */
|
||||
{ "5.4.D", "\1ABPQRSUYbdfgjmnx9", 0 },
|
||||
/* Smart-UPS 600 */
|
||||
{ "D9", "789ABCEFGKLMNOPQRSUVWXYZ", 0 },
|
||||
{ "D8", "789ABCEFGKLMNOPQRSUVWXYZ", 0 },
|
||||
{ "D7", "789ABCEFGKLMNOPQRSUVWXYZ", 0 },
|
||||
{ "D6", "789ABCEFGKLMNOPQRSUVWXYZ", 0 },
|
||||
{ "D5", "789ABCEFGKLMNOPQRSUVWXYZ", 0 },
|
||||
{ "D4", "789ABCEFGKLMNOPQRSUVWXYZ", 0 },
|
||||
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
91
drivers/baytech-mib.c
Normal file
91
drivers/baytech-mib.c
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/* baytech-mib.c - data to monitor BayTech PDUs
|
||||
*
|
||||
* Copyright (C) 2009
|
||||
* Opengear <support@opengear.com>
|
||||
* Arnaud Quette <arnaud.quette@free.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "baytech-mib.h"
|
||||
|
||||
#define BAYTECH_MIB_VERSION "4031"
|
||||
|
||||
/* Baytech MIB */
|
||||
#define BAYTECH_OID_MIB ".1.3.6.1.4.1.4779"
|
||||
#define BAYTECH_OID_MODEL_NAME ".1.3.6.1.4.1.4779.1.3.5.2.1.24.1"
|
||||
|
||||
static info_lkp_t outlet_status_info[] = {
|
||||
{ -1, "error" },
|
||||
{ 0, "off" },
|
||||
{ 1, "on" },
|
||||
{ 2, "cycling" }, /* transitional status */
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
/* Snmp2NUT lookup table for BayTech MIBs */
|
||||
static snmp_info_t baytech_mib[] = {
|
||||
/* Device page */
|
||||
{ "device.mfr", ST_FLAG_STRING, SU_INFOSIZE, NULL, "BayTech",
|
||||
SU_FLAG_STATIC | SU_FLAG_ABSENT | SU_FLAG_OK, NULL, NULL },
|
||||
{ "device.model", ST_FLAG_STRING, SU_INFOSIZE, ".1.3.6.1.4.1.4779.1.3.5.2.1.24.1",
|
||||
"Generic SNMP PDU", SU_FLAG_STATIC | SU_FLAG_OK, NULL, NULL },
|
||||
{ "device.serial", ST_FLAG_STRING, SU_INFOSIZE, ".1.3.6.1.4.1.4779.1.1.2.0", "",
|
||||
SU_FLAG_STATIC | SU_FLAG_OK, NULL, NULL },
|
||||
{ "device.type", ST_FLAG_STRING, SU_INFOSIZE, NULL, "pdu",
|
||||
SU_FLAG_STATIC | SU_FLAG_ABSENT | SU_FLAG_OK, NULL, NULL },
|
||||
|
||||
/* UPS page */
|
||||
{ "ups.mfr", ST_FLAG_STRING, SU_INFOSIZE, NULL, "Baytech",
|
||||
SU_FLAG_STATIC | SU_FLAG_ABSENT | SU_FLAG_OK, NULL, NULL },
|
||||
{ "ups.model", ST_FLAG_STRING, SU_INFOSIZE, ".1.3.6.1.4.1.4779.1.3.5.2.1.24.1",
|
||||
"Generic SNMP PDU", SU_FLAG_STATIC | SU_FLAG_OK, NULL, NULL },
|
||||
{ "ups.id", ST_FLAG_STRING, SU_INFOSIZE, ".1.3.6.1.4.1.4779.1.1.3.0",
|
||||
"unknown", SU_FLAG_STATIC | SU_FLAG_OK, NULL, NULL },
|
||||
{ "ups.serial", ST_FLAG_STRING, SU_INFOSIZE, ".1.3.6.1.4.1.4779.1.1.2.0", "",
|
||||
SU_FLAG_STATIC | SU_FLAG_OK, NULL, NULL },
|
||||
{ "ups.firmware", ST_FLAG_STRING, SU_INFOSIZE, ".1.3.6.1.4.1.4779.1.1.1.0", "",
|
||||
SU_FLAG_STATIC | SU_FLAG_OK, NULL },
|
||||
{ "ups.type", ST_FLAG_STRING, SU_INFOSIZE, NULL, "pdu",
|
||||
SU_FLAG_STATIC | SU_FLAG_ABSENT | SU_FLAG_OK, NULL, NULL },
|
||||
{ "ups.macaddr", ST_FLAG_STRING, SU_INFOSIZE, ".1.3.6.1.2.1.2.2.1.6.2",
|
||||
"", SU_FLAG_STATIC | SU_FLAG_OK, NULL, NULL },
|
||||
{ "ups.temperature", 0, 0.1, ".1.3.6.1.4.1.4779.1.3.5.5.1.10.2.1", NULL, 0, NULL, NULL },
|
||||
|
||||
/* Outlet page */
|
||||
{ "outlet.id", 0, 1, NULL, "0", SU_FLAG_STATIC | SU_FLAG_ABSENT | SU_FLAG_OK, NULL },
|
||||
{ "outlet.desc", ST_FLAG_RW | ST_FLAG_STRING, 20, NULL, "All outlets",
|
||||
SU_FLAG_STATIC | SU_FLAG_ABSENT | SU_FLAG_OK, NULL },
|
||||
{ "outlet.count", 0, 1, ".1.3.6.1.4.1.4779.1.3.5.2.1.15.1", "0", 0, NULL },
|
||||
{ "outlet.current", 0, 0.1, ".1.3.6.1.4.1.4779.1.3.5.5.1.6.2.1", NULL, 0, NULL, NULL },
|
||||
{ "outlet.voltage", 0, 0.1, ".1.3.6.1.4.1.4779.1.3.5.5.1.8.2.1", NULL, 0, NULL, NULL },
|
||||
|
||||
/* outlet template definition */
|
||||
{ "outlet.%i.status", ST_FLAG_STRING, SU_INFOSIZE, ".1.3.6.1.4.1.4779.1.3.5.3.1.3.1.%i", NULL, SU_OUTLET, &outlet_status_info[0], NULL },
|
||||
{ "outlet.%i.desc", ST_FLAG_RW | ST_FLAG_STRING, SU_INFOSIZE, ".1.3.6.1.4.1.4779.1.3.5.3.1.4.1.%i", NULL, SU_OUTLET, NULL, NULL },
|
||||
{ "outlet.%i.id", 0, 1, ".1.3.6.1.4.1.4779.1.3.5.6.1.3.2.1.%i", "%i", SU_FLAG_STATIC | SU_FLAG_ABSENT | SU_OUTLET | SU_FLAG_OK, NULL, NULL },
|
||||
{ "outlet.%i.switchable", 0, 1, ".1.3.6.1.4.1.4779.1.3.5.3.1.1.1.%i", "yes", SU_FLAG_STATIC | SU_OUTLET, NULL, NULL },
|
||||
|
||||
/* instant commands. */
|
||||
{ "outlet.%i.load.off", 0, 0, ".1.3.6.1.4.1.4779.1.3.5.3.1.3.1.%i", NULL, SU_TYPE_CMD | SU_OUTLET, NULL, NULL },
|
||||
{ "outlet.%i.load.on", 0, 1, ".1.3.6.1.4.1.4779.1.3.5.3.1.3.1.%i", NULL, SU_TYPE_CMD | SU_OUTLET, NULL, NULL },
|
||||
|
||||
/* end of structure. */
|
||||
{ NULL, 0, 0, NULL, NULL, 0, NULL, NULL }
|
||||
};
|
||||
|
||||
mib2nut_info_t baytech = { "baytech", BAYTECH_MIB_VERSION, "", BAYTECH_OID_MODEL_NAME, baytech_mib };
|
||||
9
drivers/baytech-mib.h
Normal file
9
drivers/baytech-mib.h
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#ifndef BAYTECH_MIB_H
|
||||
#define BAYTECH_MIB_H
|
||||
|
||||
#include "main.h"
|
||||
#include "snmp-ups.h"
|
||||
|
||||
extern mib2nut_info_t baytech;
|
||||
|
||||
#endif /* BAYTECH_MIB_H */
|
||||
1665
drivers/bcmxcp.c
Normal file
1665
drivers/bcmxcp.c
Normal file
File diff suppressed because it is too large
Load diff
364
drivers/bcmxcp.h
Normal file
364
drivers/bcmxcp.h
Normal file
|
|
@ -0,0 +1,364 @@
|
|||
/*
|
||||
* bcmxcp.h -- header for BCM/XCP module
|
||||
*/
|
||||
|
||||
#ifndef _POWERWARE_H
|
||||
#define _POWERWARE_H
|
||||
|
||||
#include "timehead.h"
|
||||
|
||||
#define PW_MAX_TRY 3 /* How many times we try to send data. */
|
||||
|
||||
#define PW_COMMAND_START_BYTE (unsigned char)0xAB
|
||||
#define PW_LAST_SEQ (unsigned char)0x80 /* bit flag to indicate final sequence */
|
||||
#define PW_SEQ_MASK (unsigned char)0x7F /* bit mask to extract just the sequence # */
|
||||
|
||||
#define PW_ANSWER_MAX_SIZE 256
|
||||
|
||||
/* No Autorisation required */
|
||||
#define PW_ID_BLOCK_REQ (unsigned char)0x31 /* Model name, ... length 1 */
|
||||
#define PW_STATUS_REQ (unsigned char)0x33 /* On Line, On Bypass, ... length 1-2 */
|
||||
#define PW_METER_BLOCK_REQ (unsigned char)0x34 /* Current UPS status (Load, utility,...) length 1 */
|
||||
#define PW_CUR_ALARM_REQ (unsigned char)0x35 /* Current alarm and event request. length 1 */
|
||||
#define PW_CONFIG_BLOC_REQ (unsigned char)0x36 /* Model serial#, ... length 1 */
|
||||
#define PW_BAT_TEST_REQ (unsigned char)0x3B /* Charging, floating, ... length 1 */
|
||||
#define PW_LIMIT_BLOCK_REQ (unsigned char)0x3C /* Configuration (Bypass thresholds,...). length 1 */
|
||||
#define PW_TEST_RESULT_REQ (unsigned char)0x3F /* ??. length 1 */
|
||||
#define PW_COMMAND_LIST_REQ (unsigned char)0x40 /* Available commands. length 1 */
|
||||
#define PW_OUT_MON_BLOCK_REQ (unsigned char)0x41 /* Outlet monitor request length 1 */
|
||||
#define PW_COM_CAP_REQ (unsigned char)0x42 /* Request communication capabilities. length 2 */
|
||||
#define PW_UPS_TOP_DATA_REQ (unsigned char)0x43 /* Requsest ups topology data requset. length 1 */
|
||||
|
||||
/* Need autorisation before this commands */
|
||||
#define PW_UPS_ON (unsigned char)0x89 /* UPS on command. length 1-2 */
|
||||
#define PW_LOAD_OFF_RESTART (unsigned char)0x8A /* Delayed LoadPowerOff & Restart command. length 2-4 */
|
||||
#define PW_UPS_OFF (unsigned char)0x8B /* UPS off command. length 1-2 */
|
||||
#define PW_UPS_ON_TIME (unsigned char)0x91 /* Scheduled UPS on in n minutes. length 3-4 */
|
||||
#define PW_UPS_OFF_TIME (unsigned char)0x93 /* Scheduled UPS off in n minutes. length 3-4 */
|
||||
#define PW_SET_CONF_COMMAND (unsigned char)0x95 /* Set configuration command. length 4 */
|
||||
#define PW_SET_OUTLET_COMMAND (unsigned char)0x97 /* Set outlet parameter command length 5. not in 5115 */
|
||||
#define PW_SET_COM_COMMAND (unsigned char)0x98 /* Set communication parameter command. length 5 */
|
||||
#define PW_SET_REQ_ONLY_MODE (unsigned char)0xA0 /* Set request only mode command. length 1 */
|
||||
#define PW_INIT_BAT_TEST (unsigned char)0xB1 /* Initiate battery test command. length 3 */
|
||||
#define PW_INIT_SYS_TEST (unsigned char)0xB2 /* Initiate general system test command. length 2 */
|
||||
|
||||
/* Config block offsets */
|
||||
#define BCMXCP_CONFIG_BLOCK_MACHINE_TYPE_CODE 0
|
||||
#define BCMXCP_CONFIG_BLOCK_MODEL_NUMBER 2
|
||||
#define BCMXCP_CONFIG_BLOCK_MODEL_CONF_WORD 4
|
||||
#define BCMXCP_CONFIG_BLOCK_INPUT_FREQ_DEV_LIMIT 6
|
||||
#define BCMXCP_CONFIG_BLOCK_NOMINAL_OUTPUT_VOLTAGE 8
|
||||
#define BCMXCP_CONFIG_BLOCK_NOMINAL_OUTPUT_FREQ 10
|
||||
#define BCMXCP_CONFIG_BLOCK_OUTPUT_PHASE_ANGLE 12
|
||||
#define BCMXCP_CONFIG_BLOCK_HW_MODULES_INSTALLED_WORD1 14
|
||||
#define BCMXCP_CONFIG_BLOCK_HW_MODULES_INSTALLED_BYTE3 16 /* KEEP THIS UNTILL PARSING OK. USE THIS BYTE. */
|
||||
#define BCMXCP_CONFIG_BLOCK_HW_MODULES_INSTALLED_WORD2 16
|
||||
#define BCMXCP_CONFIG_BLOCK_HW_MODULES_INSTALLED_WORD3 18
|
||||
#define BCMXCP_CONFIG_BLOCK_HW_MODULES_INSTALLED_WORD4 20
|
||||
#define BCMXCP_CONFIG_BLOCK_BATTERY_DATA_WORD1 22 /* Undefined at this time.*/
|
||||
#define BCMXCP_CONFIG_BLOCK_BATTERY_DATA_WORD2 24 /* Per cell inverter shutdown voltage at full rated load. (volt/cell)* 100 */
|
||||
#define BCMXCP_CONFIG_BLOCK_BATTERY_DATA_WORD3 26 /* LOW BYTE Number of battery strings. HIGH BYTE undefined at this time.*/
|
||||
#define BCMXCP_CONFIG_BLOCK_EXTENDED_BLOCK_LENGTH 47
|
||||
#define BCMXCP_CONFIG_BLOCK_SERIAL_NUMBER 64
|
||||
|
||||
/* Index for Extende Limits block offsets */
|
||||
#define BCMXCP_EXT_LIMITS_BLOCK_NOMINAL_INPUT_VOLTAGE 0
|
||||
#define BCMXCP_EXT_LIMITS_BLOCK_NOMINAL_INPUT_FREQ 2
|
||||
#define BCMXCP_EXT_LIMITS_BLOCK_NOMINAL_TRUE_POWER 4
|
||||
#define BCMXCP_EXT_LIMITS_BLOCK_COMM_SPEC_VERSION 6
|
||||
#define BCMXCP_EXT_LIMITS_BLOCK_FREQ_DEV_LIMIT 8
|
||||
#define BCMXCP_EXT_LIMITS_BLOCK_VOLTAGE_LOW_DEV_LIMIT 10
|
||||
#define BCMXCP_EXT_LIMITS_BLOCK_VOLTAGE_HIGE_DEV_LIMIT 12
|
||||
#define BCMXCP_EXT_LIMITS_BLOCK_PHASE_DEV_LIMIT 14
|
||||
#define BCMXCP_EXT_LIMITS_BLOCK_LOW_BATT_WARNING 16
|
||||
#define BCMXCP_EXT_LIMITS_BLOCK_HORN_STATUS 17
|
||||
#define BCMXCP_EXT_LIMITS_BLOCK_MIN_INPUT_VOLTAGE 18
|
||||
#define BCMXCP_EXT_LIMITS_BLOCK_MAX_INPUT_VOLTAGE 20
|
||||
#define BCMXCP_EXT_LIMITS_BLOCK_RETURN_STAB_DELAY 22
|
||||
#define BCMXCP_EXT_LIMITS_BLOCK_BATT_CAPACITY_RETURN 24
|
||||
#define BCMXCP_EXT_LIMITS_BLOCK_AMBIENT_TEMP_LOW 25
|
||||
#define BCMXCP_EXT_LIMITS_BLOCK_AMBIENT_TEMP_HIGE 26
|
||||
|
||||
/* Meter map offsets used */
|
||||
#define BCMXCP_METER_MAP_OUTPUT_VA 23
|
||||
#define BCMXCP_METER_MAP_LOAD_CURR_PHASE_A 65
|
||||
#define BCMXCP_METER_MAP_LOAD_CURR_PHASE_A_BAR_CHART 68
|
||||
#define BCMXCP_METER_MAP_OUTPUT_VA_BAR_CHART 71
|
||||
|
||||
/* Indexes for alarm map */
|
||||
#define BCMXCP_ALARM_INVERTER_AC_OVER_VOLTAGE 0
|
||||
#define BCMXCP_ALARM_INVERTER_AC_UNDER_VOLTAGE 1
|
||||
#define BCMXCP_ALARM_INVERTER_OVER_OR_UNDER_FREQ 2
|
||||
#define BCMXCP_ALARM_BYPASS_AC_OVER_VOLTAGE 3
|
||||
#define BCMXCP_ALARM_BYPASS_AC_UNDER_VOLTAGE 4
|
||||
#define BCMXCP_ALARM_BYPASS_OVER_OR_UNDER_FREQ 5
|
||||
#define BCMXCP_ALARM_INPUT_AC_OVER_VOLTAGE 6
|
||||
#define BCMXCP_ALARM_INPUT_AC_UNDER_VOLTAGE 7
|
||||
#define BCMXCP_ALARM_INPUT_UNDER_OR_OVER_FREQ 8
|
||||
#define BCMXCP_ALARM_OUTPUT_OVER_VOLTAGE 9
|
||||
#define BCMXCP_ALARM_OUTPUT_UNDER_VOLTAGE 10
|
||||
#define BCMXCP_ALARM_OUTPUT_UNDER_OR_OVER_FREQ 11
|
||||
#define BCMXCP_ALARM_REMOTE_EMERGENCY_PWR_OFF 12
|
||||
#define BCMXCP_ALARM_REMOTE_GO_TO_BYPASS 13
|
||||
#define BCMXCP_ALARM_BUILDING_ALARM_6 14
|
||||
#define BCMXCP_ALARM_BUILDING_ALARM_5 15
|
||||
#define BCMXCP_ALARM_BUILDING_ALARM_4 16
|
||||
#define BCMXCP_ALARM_BUILDING_ALARM_3 17
|
||||
#define BCMXCP_ALARM_BUILDING_ALARM_2 18
|
||||
#define BCMXCP_ALARM_BUILDING_ALARM_1 19
|
||||
#define BCMXCP_ALARM_STATIC_SWITCH_OVER_TEMP 20
|
||||
#define BCMXCP_ALARM_CHARGER_OVER_TEMP 21
|
||||
#define BCMXCP_ALARM_CHARGER_LOGIC_PWR_FAIL 22
|
||||
#define BCMXCP_ALARM_CHARGER_OVER_VOLTAGE_OR_CURRENT 23
|
||||
#define BCMXCP_ALARM_INVERTER_OVER_TEMP 24
|
||||
#define BCMXCP_ALARM_OUTPUT_OVERLOAD 25
|
||||
#define BCMXCP_ALARM_RECTIFIER_INPUT_OVER_CURRENT 26
|
||||
#define BCMXCP_ALARM_INVERTER_OUTPUT_OVER_CURRENT 27
|
||||
#define BCMXCP_ALARM_DC_LINK_OVER_VOLTAGE 28
|
||||
#define BCMXCP_ALARM_DC_LINK_UNDER_VOLTAGE 29
|
||||
#define BCMXCP_ALARM_RECTIFIER_FAILED 30
|
||||
#define BCMXCP_ALARM_INVERTER_FAULT 31
|
||||
#define BCMXCP_ALARM_BATTERY_CONNECTOR_FAIL 32
|
||||
#define BCMXCP_ALARM_BYPASS_BREAKER_FAIL 33
|
||||
#define BCMXCP_ALARM_CHARGER_FAIL 34
|
||||
#define BCMXCP_ALARM_RAMP_UP_FAILED 35
|
||||
#define BCMXCP_ALARM_STATIC_SWITCH_FAILED 36
|
||||
#define BCMXCP_ALARM_ANALOG_AD_REF_FAIL 37
|
||||
#define BCMXCP_ALARM_BYPASS_UNCALIBRATED 38
|
||||
#define BCMXCP_ALARM_RECTIFIER_UNCALIBRATED 39
|
||||
#define BCMXCP_ALARM_OUTPUT_UNCALIBRATED 40
|
||||
#define BCMXCP_ALARM_INVERTER_UNCALIBRATED 41
|
||||
#define BCMXCP_ALARM_DC_VOLT_UNCALIBRATED 42
|
||||
#define BCMXCP_ALARM_OUTPUT_CURRENT_UNCALIBRATED 43
|
||||
#define BCMXCP_ALARM_RECTIFIER_CURRENT_UNCALIBRATED 44
|
||||
#define BCMXCP_ALARM_BATTERY_CURRENT_UNCALIBRATED 45
|
||||
#define BCMXCP_ALARM_INVERTER_ON_OFF_STAT_FAIL 46
|
||||
#define BCMXCP_ALARM_BATTERY_CURRENT_LIMIT 47
|
||||
#define BCMXCP_ALARM_INVERTER_STARTUP_FAIL 48
|
||||
#define BCMXCP_ALARM_ANALOG_BOARD_AD_STAT_FAIL 49
|
||||
#define BCMXCP_ALARM_OUTPUT_CURRENT_OVER_100 50
|
||||
#define BCMXCP_ALARM_BATTERY_GROUND_FAULT 51
|
||||
#define BCMXCP_ALARM_WAITING_FOR_CHARGER_SYNC 52
|
||||
#define BCMXCP_ALARM_NV_RAM_FAIL 53
|
||||
#define BCMXCP_ALARM_ANALOG_BOARD_AD_TIMEOUT 54
|
||||
#define BCMXCP_ALARM_SHUTDOWN_IMMINENT 55
|
||||
#define BCMXCP_ALARM_BATTERY_LOW 56
|
||||
#define BCMXCP_ALARM_UTILITY_FAIL 57
|
||||
#define BCMXCP_ALARM_OUTPUT_SHORT_CIRCUIT 58
|
||||
#define BCMXCP_ALARM_UTILITY_NOT_PRESENT 59
|
||||
#define BCMXCP_ALARM_FULL_TIME_CHARGING 60
|
||||
#define BCMXCP_ALARM_FAST_BYPASS_COMMAND 61
|
||||
#define BCMXCP_ALARM_AD_ERROR 62
|
||||
#define BCMXCP_ALARM_INTERNAL_COM_FAIL 63
|
||||
#define BCMXCP_ALARM_RECTIFIER_SELFTEST_FAIL 64
|
||||
#define BCMXCP_ALARM_RECTIFIER_EEPROM_FAIL 65
|
||||
#define BCMXCP_ALARM_RECTIFIER_EPROM_FAIL 66
|
||||
#define BCMXCP_ALARM_INPUT_LINE_VOLTAGE_LOSS 67
|
||||
#define BCMXCP_ALARM_BATTERY_DC_OVER_VOLTAGE 68
|
||||
#define BCMXCP_ALARM_POWER_SUPPLY_OVER_TEMP 69
|
||||
#define BCMXCP_ALARM_POWER_SUPPLY_FAIL 70
|
||||
#define BCMXCP_ALARM_POWER_SUPPLY_5V_FAIL 71
|
||||
#define BCMXCP_ALARM_POWER_SUPPLY_12V_FAIL 72
|
||||
#define BCMXCP_ALARM_HEATSINK_OVER_TEMP 73
|
||||
#define BCMXCP_ALARM_HEATSINK_TEMP_SENSOR_FAIL 74
|
||||
#define BCMXCP_ALARM_RECTIFIER_CURRENT_OVER_125 75
|
||||
#define BCMXCP_ALARM_RECTIFIER_FAULT_INTERRUPT_FAIL 76
|
||||
#define BCMXCP_ALARM_RECTIFIER_POWER_CAPASITOR_FAIL 77
|
||||
#define BCMXCP_ALARM_INVERTER_PROGRAM_STACK_ERROR 78
|
||||
#define BCMXCP_ALARM_INVERTER_BOARD_SELFTEST_FAIL 79
|
||||
#define BCMXCP_ALARM_INVERTER_AD_SELFTEST_FAIL 80
|
||||
#define BCMXCP_ALARM_INVERTER_RAM_SELFTEST_FAIL 81
|
||||
#define BCMXCP_ALARM_NV_MEMORY_CHECKSUM_FAIL 82
|
||||
#define BCMXCP_ALARM_PROGRAM_CHECKSUM_FAIL 83
|
||||
#define BCMXCP_ALARM_INVERTER_CPU_SELFTEST_FAIL 84
|
||||
#define BCMXCP_ALARM_NETWORK_NOT_RESPONDING 85
|
||||
#define BCMXCP_ALARM_FRONT_PANEL_SELFTEST_FAIL 86
|
||||
#define BCMXCP_ALARM_NODE_EEPROM_VERIFICATION_ERROR 87
|
||||
#define BCMXCP_ALARM_OUTPUT_AC_OVER_VOLT_TEST_FAIL 88
|
||||
#define BCMXCP_ALARM_OUTPUT_DC_OVER_VOLTAGE 89
|
||||
#define BCMXCP_ALARM_INPUT_PHASE_ROTATION_ERROR 90
|
||||
#define BCMXCP_ALARM_INVERTER_RAMP_UP_TEST_FAILED 91
|
||||
#define BCMXCP_ALARM_INVERTER_OFF_COMMAND 92
|
||||
#define BCMXCP_ALARM_INVERTER_ON_COMMAND 93
|
||||
#define BCMXCP_ALARM_TO_BYPASS_COMMAND 94
|
||||
#define BCMXCP_ALARM_FROM_BYPASS_COMMAND 95
|
||||
#define BCMXCP_ALARM_AUTO_MODE_COMMAND 96
|
||||
#define BCMXCP_ALARM_EMERGENCY_SHUTDOWN_COMMAND 97
|
||||
#define BCMXCP_ALARM_SETUP_SWITCH_OPEN 98
|
||||
#define BCMXCP_ALARM_INVERTER_OVER_VOLT_INT 99
|
||||
#define BCMXCP_ALARM_INVERTER_UNDER_VOLT_INT 100
|
||||
#define BCMXCP_ALARM_ABSOLUTE_DCOV_ACOV 101
|
||||
#define BCMXCP_ALARM_PHASE_A_CURRENT_LIMIT 102
|
||||
#define BCMXCP_ALARM_PHASE_B_CURRENT_LIMIT 103
|
||||
#define BCMXCP_ALARM_PHASE_C_CURRENT_LIMIT 104
|
||||
#define BCMXCP_ALARM_BYPASS_NOT_AVAILABLE 105
|
||||
#define BCMXCP_ALARM_RECTIFIER_BREAKER_OPEN 106
|
||||
#define BCMXCP_ALARM_BATTERY_CONTACTOR_OPEN 107
|
||||
#define BCMXCP_ALARM_INVERTER_CONTACTOR_OPEN 108
|
||||
#define BCMXCP_ALARM_BYPASS_BREAKER_OPEN 109
|
||||
#define BCMXCP_ALARM_INV_BOARD_ACOV_INT_TEST_FAIL 110
|
||||
#define BCMXCP_ALARM_INVERTER_OVER_TEMP_TRIP 111
|
||||
#define BCMXCP_ALARM_INV_BOARD_ACUV_INT_TEST_FAIL 112
|
||||
#define BCMXCP_ALARM_INVERTER_VOLTAGE_FEEDBACK_ERROR 113
|
||||
#define BCMXCP_ALARM_DC_UNDER_VOLTAGE_TIMEOUT 114
|
||||
#define BCMXCP_ALARM_AC_UNDER_VOLTAGE_TIMEOUT 115
|
||||
#define BCMXCP_ALARM_DC_UNDER_VOLTAGE_WHILE_CHARGE 116
|
||||
#define BCMXCP_ALARM_INVERTER_VOLTAGE_BIAS_ERROR 117
|
||||
#define BCMXCP_ALARM_RECTIFIER_PHASE_ROTATION 118
|
||||
#define BCMXCP_ALARM_BYPASS_PHASER_ROTATION 119
|
||||
#define BCMXCP_ALARM_SYSTEM_INTERFACE_BOARD_FAIL 120
|
||||
#define BCMXCP_ALARM_PARALLEL_BOARD_FAIL 121
|
||||
#define BCMXCP_ALARM_LOST_LOAD_SHARING_PHASE_A 122
|
||||
#define BCMXCP_ALARM_LOST_LOAD_SHARING_PHASE_B 123
|
||||
#define BCMXCP_ALARM_LOST_LOAD_SHARING_PHASE_C 124
|
||||
#define BCMXCP_ALARM_DC_OVER_VOLTAGE_TIMEOUT 125
|
||||
#define BCMXCP_ALARM_BATTERY_TOTALLY_DISCHARGED 126
|
||||
#define BCMXCP_ALARM_INVERTER_PHASE_BIAS_ERROR 127
|
||||
#define BCMXCP_ALARM_INVERTER_VOLTAGE_BIAS_ERROR_2 128
|
||||
#define BCMXCP_ALARM_DC_LINK_BLEED_COMPLETE 129
|
||||
#define BCMXCP_ALARM_LARGE_CHARGER_INPUT_CURRENT 130
|
||||
#define BCMXCP_ALARM_INV_VOLT_TOO_LOW_FOR_RAMP_LEVEL 131
|
||||
#define BCMXCP_ALARM_LOSS_OF_REDUNDANCY 132
|
||||
#define BCMXCP_ALARM_LOSS_OF_SYNC_BUS 133
|
||||
#define BCMXCP_ALARM_RECTIFIER_BREAKER_SHUNT_TRIP 134
|
||||
#define BCMXCP_ALARM_LOSS_OF_CHARGER_SYNC 135
|
||||
#define BCMXCP_ALARM_INVERTER_LOW_LEVEL_TEST_TIMEOUT 136
|
||||
#define BCMXCP_ALARM_OUTPUT_BREAKER_OPEN 137
|
||||
#define BCMXCP_ALARM_CONTROL_POWER_ON 138
|
||||
#define BCMXCP_ALARM_INVERTER_ON 139
|
||||
#define BCMXCP_ALARM_CHARGER_ON 140
|
||||
#define BCMXCP_ALARM_BYPASS_ON 141
|
||||
#define BCMXCP_ALARM_BYPASS_POWER_LOSS 142
|
||||
#define BCMXCP_ALARM_ON_MANUAL_BYPASS 143
|
||||
#define BCMXCP_ALARM_BYPASS_MANUAL_TURN_OFF 144
|
||||
#define BCMXCP_ALARM_INVERTER_BLEEDING_DC_LINK_VOLT 145
|
||||
#define BCMXCP_ALARM_CPU_ISR_ERROR 146
|
||||
#define BCMXCP_ALARM_SYSTEM_ISR_RESTART 147
|
||||
#define BCMXCP_ALARM_PARALLEL_DC 148
|
||||
#define BCMXCP_ALARM_BATTERY_NEEDS_SERVICE 149
|
||||
#define BCMXCP_ALARM_BATTERY_CHARGING 150
|
||||
#define BCMXCP_ALARM_BATTERY_NOT_CHARGED 151
|
||||
#define BCMXCP_ALARM_DISABLED_BATTERY_TIME 152
|
||||
#define BCMXCP_ALARM_SERIES_7000_ENABLE 153
|
||||
#define BCMXCP_ALARM_OTHER_UPS_ON 154
|
||||
#define BCMXCP_ALARM_PARALLEL_INVERTER 155
|
||||
#define BCMXCP_ALARM_UPS_IN_PARALLEL 156
|
||||
#define BCMXCP_ALARM_OUTPUT_BREAKER_REALY_FAIL 157
|
||||
#define BCMXCP_ALARM_CONTROL_POWER_OFF 158
|
||||
#define BCMXCP_ALARM_LEVEL_2_OVERLOAD_PHASE_A 159
|
||||
#define BCMXCP_ALARM_LEVEL_2_OVERLOAD_PHASE_B 160
|
||||
#define BCMXCP_ALARM_LEVEL_2_OVERLOAD_PHASE_C 161
|
||||
#define BCMXCP_ALARM_LEVEL_3_OVERLOAD_PHASE_A 162
|
||||
#define BCMXCP_ALARM_LEVEL_3_OVERLOAD_PHASE_B 163
|
||||
#define BCMXCP_ALARM_LEVEL_3_OVERLOAD_PHASE_C 164
|
||||
#define BCMXCP_ALARM_LEVEL_4_OVERLOAD_PHASE_A 165
|
||||
#define BCMXCP_ALARM_LEVEL_4_OVERLOAD_PHASE_B 166
|
||||
#define BCMXCP_ALARM_LEVEL_4_OVERLOAD_PHASE_C 167
|
||||
#define BCMXCP_ALARM_UPS_ON_BATTERY 168
|
||||
#define BCMXCP_ALARM_UPS_ON_BYPASS 169
|
||||
#define BCMXCP_ALARM_LOAD_DUMPED 170
|
||||
#define BCMXCP_ALARM_LOAD_ON_INVERTER 171
|
||||
#define BCMXCP_ALARM_UPS_ON_COMMAND 172
|
||||
#define BCMXCP_ALARM_UPS_OFF_COMMAND 173
|
||||
#define BCMXCP_ALARM_LOW_BATTERY_SHUTDOWN 174
|
||||
#define BCMXCP_ALARM_AUTO_ON_ENABLED 175
|
||||
#define BCMXCP_ALARM_SOFTWARE_INCOMPABILITY_DETECTED 176
|
||||
#define BCMXCP_ALARM_INVERTER_TEMP_SENSOR_FAILED 177
|
||||
#define BCMXCP_ALARM_DC_START_OCCURED 178
|
||||
#define BCMXCP_ALARM_IN_PARALLEL_OPERATION 179
|
||||
#define BCMXCP_ALARM_SYNCING_TO_BYPASS 180
|
||||
#define BCMXCP_ALARM_RAMPING_UPS_UP 181
|
||||
#define BCMXCP_ALARM_INVERTER_ON_DELAY 182
|
||||
#define BCMXCP_ALARM_CHARGER_ON_DELAY 183
|
||||
#define BCMXCP_ALARM_WAITING_FOR_UTIL_INPUT 184
|
||||
#define BCMXCP_ALARM_CLOSE_BYPASS_BREAKER 185
|
||||
#define BCMXCP_ALARM_TEMPORARY_BYPASS_OPERATION 186
|
||||
#define BCMXCP_ALARM_SYNCING_TO_OUTPUT 187
|
||||
#define BCMXCP_ALARM_BYPASS_FAILURE 188
|
||||
#define BCMXCP_ALARM_AUTO_OFF_COMMAND_EXECUTED 189
|
||||
#define BCMXCP_ALARM_AUTO_ON_COMMAND_EXECUTED 190
|
||||
#define BCMXCP_ALARM_BATTERY_TEST_FAILED 191
|
||||
#define BCMXCP_ALARM_FUSE_FAIL 192
|
||||
#define BCMXCP_ALARM_FAN_FAIL 193
|
||||
#define BCMXCP_ALARM_SITE_WIRING_FAULT 194
|
||||
#define BCMXCP_ALARM_BACKFEED_CONTACTOR_FAIL 195
|
||||
#define BCMXCP_ALARM_ON_BUCK 196
|
||||
#define BCMXCP_ALARM_ON_BOOST 197
|
||||
#define BCMXCP_ALARM_ON_DOUBLE_BOOST 198
|
||||
#define BCMXCP_ALARM_BATTERIES_DISCONNECTED 199
|
||||
#define BCMXCP_ALARM_UPS_CABINET_OVER_TEMP 200
|
||||
#define BCMXCP_ALARM_TRANSFORMER_OVER_TEMP 201
|
||||
#define BCMXCP_ALARM_AMBIENT_UNDER_TEMP 202
|
||||
#define BCMXCP_ALARM_AMBIENT_OVER_TEMP 203
|
||||
#define BCMXCP_ALARM_CABINET_DOOR_OPEN 204
|
||||
#define BCMXCP_ALARM_CABINET_DOOR_OPEN_VOLT_PRESENT 205
|
||||
#define BCMXCP_ALARM_AUTO_SHUTDOWN_PENDING 206
|
||||
#define BCMXCP_ALARM_TAP_SWITCHING_REALY_PENDING 207
|
||||
#define BCMXCP_ALARM_UNABLE_TO_CHARGE_BATTERIES 208
|
||||
#define BCMXCP_ALARM_STARTUP_FAILURE_CHECK_EPO 209
|
||||
#define BCMXCP_ALARM_AUTOMATIC_STARTUP_PENDING 210
|
||||
#define BCMXCP_ALARM_MODEM_FAILED 211
|
||||
#define BCMXCP_ALARM_INCOMING_MODEM_CALL_STARTED 212
|
||||
#define BCMXCP_ALARM_OUTGOING_MODEM_CALL_STARTED 213
|
||||
#define BCMXCP_ALARM_MODEM_CONNECTION_ESTABLISHED 214
|
||||
#define BCMXCP_ALARM_MODEM_CALL_COMPLETED_SUCCESS 215
|
||||
#define BCMXCP_ALARM_MODEM_CALL_COMPLETED_FAIL 216
|
||||
#define BCMXCP_ALARM_INPUT_BREAKER_FAIL 217
|
||||
#define BCMXCP_ALARM_SYSINIT_IN_PROGRESS 218
|
||||
#define BCMXCP_ALARM_AUTOCALIBRATION_FAIL 219
|
||||
#define BCMXCP_ALARM_SELECTIVE_TRIP_OF_MODULE 220
|
||||
#define BCMXCP_ALARM_INVERTER_OUTPUT_FAILURE 221
|
||||
#define BCMXCP_ALARM_ABNORMAL_OUTPUT_VOLT_AT_STARTUP 222
|
||||
#define BCMXCP_ALARM_RECTIFIER_OVER_TEMP 223
|
||||
#define BCMXCP_ALARM_CONFIG_ERROR 224
|
||||
#define BCMXCP_ALARM_REDUNDANCY_LOSS_DUE_TO_OVERLOAD 225
|
||||
#define BCMXCP_ALARM_ON_ALTERNATE_AC_SOURCE 226
|
||||
#define BCMXCP_ALARM_IN_HIGH_EFFICIENCY_MODE 227
|
||||
#define BCMXCP_ALARM_SYSTEM_NOTICE_ACTIVE 228
|
||||
#define BCMXCP_ALARM_SYSTEM_ALARM_ACTIVE 229
|
||||
#define BCMXCP_ALARM_ALTERNATE_POWER_SOURCE_NOT_AVAILABLE 230
|
||||
#define BCMXCP_ALARM_CURRENT_BALANCE_FAILURE 231
|
||||
#define BCMXCP_ALARM_CHECK_AIR_FILTER 232
|
||||
#define BCMXCP_ALARM_SUBSYSTEM_NOTICE_ACTIVE 233
|
||||
#define BCMXCP_ALARM_SUBSYSTEM_ALARM_ACTIVE 234
|
||||
#define BCMXCP_ALARM_CHARGER_ON_COMMAND 235
|
||||
#define BCMXCP_ALARM_CHARGER_OFF_COMMAND 236
|
||||
#define BCMXCP_ALARM_UPS_NORMAL 237
|
||||
#define BCMXCP_ALARM_EXTERNAL_COMMUNICATION_FAILURE 238
|
||||
|
||||
#define BCMXCP_METER_MAP_MAX 91 /* Max no of entries in BCM/XCP meter map */
|
||||
#define BCMXCP_ALARM_MAP_MAX 240 /* Max no of entries in BCM/XCP alarm map (adjusted upwards to nearest multi of 8 */
|
||||
|
||||
typedef struct { /* Entry in BCM/XCP - UPS - NUT mapping table */
|
||||
char *nut_entity; /* The NUT variable name */
|
||||
unsigned char format; /* The format of the data - float, long etc */
|
||||
unsigned int meter_block_index; /* The position of this meter in the UPS meter block */
|
||||
} BCMXCP_METER_MAP_ENTRY_t;
|
||||
|
||||
BCMXCP_METER_MAP_ENTRY_t
|
||||
bcmxcp_meter_map[BCMXCP_METER_MAP_MAX];
|
||||
|
||||
typedef struct { /* Entry in BCM/XCP - UPS mapping table */
|
||||
int alarm_block_index; /* Index of this alarm in alarm block. -1 = not existing */
|
||||
char *alarm_desc; /* Description of this alarm */
|
||||
} BCMXCP_ALARM_MAP_ENTRY_t;
|
||||
|
||||
BCMXCP_ALARM_MAP_ENTRY_t
|
||||
bcmxcp_alarm_map[BCMXCP_ALARM_MAP_MAX];
|
||||
|
||||
typedef struct { /* A place to store status info and other data not for NUT */
|
||||
unsigned char topology_mask; /* Configuration block byte 16, masks valid status bits */
|
||||
unsigned int lowbatt; /* Seconds of runtime left left when LB alarm is set */
|
||||
unsigned int shutdowndelay; /* Shutdown delay in seconds, from ups.conf */
|
||||
int alarm_on_battery; /* On Battery alarm active? */
|
||||
int alarm_low_battery; /* Battery Low alarm active? */
|
||||
} BCMXCP_STATUS_t;
|
||||
|
||||
BCMXCP_STATUS_t
|
||||
bcmxcp_status;
|
||||
|
||||
int checksum_test(const unsigned char*);
|
||||
unsigned char calc_checksum(const unsigned char *buf);
|
||||
|
||||
#endif /*_POWERWARE_H */
|
||||
|
||||
22
drivers/bcmxcp_io.h
Normal file
22
drivers/bcmxcp_io.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* bcmxcp_io.h -- header for BCM/XCP IO module
|
||||
*/
|
||||
|
||||
#ifndef BCMXCP_IO__
|
||||
#define BCMXCP_IO__
|
||||
|
||||
#include "main.h" /* for usbdrv_info_t */
|
||||
|
||||
void send_read_command(unsigned char command);
|
||||
void send_write_command(unsigned char *command, int command_length);
|
||||
int get_answer(unsigned char *data, unsigned char command);
|
||||
int command_read_sequence(unsigned char command, unsigned char *data);
|
||||
int command_write_sequence(unsigned char *command, int command_length, unsigned char *answer);
|
||||
void upsdrv_initups(void);
|
||||
void upsdrv_cleanup(void);
|
||||
void upsdrv_reconnect(void);
|
||||
void upsdrv_comm_good(void);
|
||||
|
||||
extern upsdrv_info_t comm_upsdrv_info;
|
||||
|
||||
#endif /* BCMXCP_IO__ */
|
||||
322
drivers/bcmxcp_ser.c
Normal file
322
drivers/bcmxcp_ser.c
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
#include "main.h"
|
||||
#include "bcmxcp.h"
|
||||
#include "bcmxcp_io.h"
|
||||
#include "serial.h"
|
||||
|
||||
#define PW_MAX_BAUD 5
|
||||
|
||||
#define SUBDRIVER_NAME "RS-232 communication subdriver"
|
||||
#define SUBDRIVER_VERSION "0.17"
|
||||
|
||||
/* communication driver description structure */
|
||||
upsdrv_info_t comm_upsdrv_info = {
|
||||
SUBDRIVER_NAME,
|
||||
SUBDRIVER_VERSION,
|
||||
NULL,
|
||||
0,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
struct pw_baud_rate {
|
||||
int rate;
|
||||
int name;
|
||||
} pw_baud_rates[] = {
|
||||
{ B1200, 1200 },
|
||||
{ B2400, 2400 },
|
||||
{ B4800, 4800 },
|
||||
{ B9600, 9600 },
|
||||
{ B19200, 19200 },
|
||||
};
|
||||
|
||||
unsigned char AUT[4] = {0xCF, 0x69, 0xE8, 0xD5}; /* Autorisation command */
|
||||
|
||||
static void send_command(unsigned char *command, int command_length)
|
||||
{
|
||||
int retry = 0, sent;
|
||||
unsigned char sbuf[128];
|
||||
|
||||
/* Prepare the send buffer */
|
||||
sbuf[0] = PW_COMMAND_START_BYTE;
|
||||
sbuf[1] = (unsigned char)(command_length);
|
||||
memcpy(sbuf+2, command, command_length);
|
||||
command_length += 2;
|
||||
|
||||
/* Add checksum */
|
||||
sbuf[command_length] = calc_checksum(sbuf);
|
||||
command_length += 1;
|
||||
|
||||
while (retry++ < PW_MAX_TRY) {
|
||||
|
||||
if (retry == PW_MAX_TRY)
|
||||
ser_send_char(upsfd, 0x1d); /* last retry is preceded by a ESC.*/
|
||||
|
||||
sent = ser_send_buf(upsfd, sbuf, command_length);
|
||||
|
||||
if (sent == command_length) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void send_read_command(unsigned char command)
|
||||
{
|
||||
send_command(&command, 1);
|
||||
}
|
||||
|
||||
void send_write_command(unsigned char *command, int command_length)
|
||||
{
|
||||
send_command(command, command_length);
|
||||
}
|
||||
|
||||
/* get the answer of a command from the ups. And check that the answer is for this command */
|
||||
int get_answer(unsigned char *data, unsigned char command)
|
||||
{
|
||||
unsigned char my_buf[128]; /* packet has a maximum length of 121+5 bytes */
|
||||
int length, end_length = 0, res, endblock = 0, start = 0;
|
||||
unsigned char block_number, sequence, pre_sequence = 0;
|
||||
|
||||
while (endblock != 1){
|
||||
|
||||
do {
|
||||
/* Read PW_COMMAND_START_BYTE byte */
|
||||
res = ser_get_char(upsfd, my_buf, 1, 0);
|
||||
|
||||
if (res != 1) {
|
||||
upsdebugx(1,"Receive error (PW_COMMAND_START_BYTE): %d!!!\n", res);
|
||||
return -1;
|
||||
}
|
||||
|
||||
start++;
|
||||
|
||||
} while ((my_buf[0] != PW_COMMAND_START_BYTE) && (start < 128));
|
||||
|
||||
if (start == 128) {
|
||||
ser_comm_fail("Receive error (PW_COMMAND_START_BYTE): packet not on start!!%x\n", my_buf[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Read block number byte */
|
||||
res = ser_get_char(upsfd, my_buf+1, 1, 0);
|
||||
|
||||
if (res != 1) {
|
||||
ser_comm_fail("Receive error (Block number): %d!!!\n", res);
|
||||
return -1;
|
||||
}
|
||||
|
||||
block_number = (unsigned char)my_buf[1];
|
||||
|
||||
if (command <= 0x43) {
|
||||
if ((command - 0x30) != block_number){
|
||||
ser_comm_fail("Receive error (Request command): %x!!!\n", block_number);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (command >= 0x89) {
|
||||
if ((command == 0xA0) && (block_number != 0x01)){
|
||||
ser_comm_fail("Receive error (Requested only mode command): %x!!!\n", block_number);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((command != 0xA0) && (block_number != 0x09)){
|
||||
ser_comm_fail("Receive error (Control command): %x!!!\n", block_number);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Read data length byte */
|
||||
res = ser_get_char(upsfd, my_buf+2, 1, 0);
|
||||
|
||||
if (res != 1) {
|
||||
ser_comm_fail("Receive error (length): %d!!!\n", res);
|
||||
return -1;
|
||||
}
|
||||
|
||||
length = (unsigned char)my_buf[2];
|
||||
|
||||
if (length < 1) {
|
||||
ser_comm_fail("Receive error (length): packet length %x!!!\n", length);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Read sequence byte */
|
||||
res = ser_get_char(upsfd, my_buf+3, 1, 0);
|
||||
|
||||
if (res != 1) {
|
||||
ser_comm_fail("Receive error (sequence): %d!!!\n", res);
|
||||
return -1;
|
||||
}
|
||||
|
||||
sequence = (unsigned char)my_buf[3];
|
||||
|
||||
if ((sequence & 0x80) == 0x80) {
|
||||
endblock = 1;
|
||||
}
|
||||
|
||||
if ((sequence & 0x07) != (pre_sequence + 1)) {
|
||||
ser_comm_fail("Not the right sequence received %x!!!\n", sequence);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pre_sequence = sequence;
|
||||
|
||||
/* Try to read all the remainig bytes */
|
||||
res = ser_get_buf_len(upsfd, my_buf+4, length, 1, 0);
|
||||
|
||||
if (res != length) {
|
||||
ser_comm_fail("Receive error (data): got %d bytes instead of %d!!!\n", res, length);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Get the checksum byte */
|
||||
res = ser_get_char(upsfd, my_buf+(4+length), 1, 0);
|
||||
|
||||
if (res != 1) {
|
||||
ser_comm_fail("Receive error (checksum): %x!!!\n", res);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* now we have the whole answer from the ups, we can checksum it */
|
||||
if (!checksum_test(my_buf)) {
|
||||
ser_comm_fail("checksum error! ");
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(data+end_length, my_buf+4, length);
|
||||
end_length += length;
|
||||
|
||||
}
|
||||
|
||||
ser_comm_good();
|
||||
|
||||
return end_length;
|
||||
}
|
||||
|
||||
static int command_sequence(unsigned char *command, int command_length, unsigned char *answer)
|
||||
{
|
||||
int bytes_read, retry = 0;
|
||||
|
||||
while (retry++ < PW_MAX_TRY) {
|
||||
|
||||
if (retry == PW_MAX_TRY) {
|
||||
ser_flush_in(upsfd, "", 0);
|
||||
}
|
||||
|
||||
send_write_command(command, command_length);
|
||||
|
||||
bytes_read = get_answer(answer, *command);
|
||||
|
||||
if (bytes_read > 0) {
|
||||
return bytes_read;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Sends a single command (length=1). and get the answer */
|
||||
int command_read_sequence(unsigned char command, unsigned char *answer)
|
||||
{
|
||||
int bytes_read;
|
||||
|
||||
bytes_read = command_sequence(&command, 1, answer);
|
||||
|
||||
if (bytes_read < 1) {
|
||||
ser_comm_fail("Error executing command");
|
||||
}
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
/* Sends a setup command (length > 1) */
|
||||
int command_write_sequence(unsigned char *command, int command_length, unsigned char *answer)
|
||||
{
|
||||
int bytes_read;
|
||||
|
||||
bytes_read = command_sequence(command, command_length, answer);
|
||||
|
||||
if (bytes_read < 1) {
|
||||
ser_comm_fail("Error executing command");
|
||||
}
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
void upsdrv_comm_good()
|
||||
{
|
||||
ser_comm_good();
|
||||
}
|
||||
|
||||
void pw_comm_setup(const char *port)
|
||||
{
|
||||
unsigned char command = PW_SET_REQ_ONLY_MODE;
|
||||
unsigned char answer[256];
|
||||
int i = 0, baud, mybaud = 0, ret = -1;
|
||||
|
||||
if (getval("baud_rate") != NULL)
|
||||
{
|
||||
baud = atoi(getval("baud_rate"));
|
||||
|
||||
for(i = 0; i < PW_MAX_BAUD; i++) {
|
||||
if (baud == pw_baud_rates[i].name) {
|
||||
mybaud = pw_baud_rates[i].rate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mybaud == 0) {
|
||||
fatalx(EXIT_FAILURE, "Specified baudrate \"%s\" is invalid!", getval("baud_rate"));
|
||||
}
|
||||
|
||||
ser_set_speed(upsfd, device_path, mybaud);
|
||||
ser_send_char(upsfd, 0x1d); /* send ESC to take it out of menu */
|
||||
usleep(90000);
|
||||
send_write_command(AUT, 4);
|
||||
usleep(500000);
|
||||
ret = command_sequence(&command, 1, answer);
|
||||
|
||||
if (ret > 0) {
|
||||
upslogx(LOG_INFO, "Connected to UPS on %s with baudrate %d", port, baud);
|
||||
return;
|
||||
}
|
||||
|
||||
upslogx(LOG_ERR, "No response from UPS on %s with baudrate %d", port, baud);
|
||||
}
|
||||
|
||||
upslogx(LOG_INFO, "Attempting to autodect baudrate");
|
||||
|
||||
for (i=0; i<PW_MAX_BAUD; i++) {
|
||||
|
||||
ser_set_speed(upsfd, device_path, pw_baud_rates[i].rate);
|
||||
ser_send_char(upsfd, 0x1d); /* send ESC to take it out of menu */
|
||||
usleep(90000);
|
||||
send_write_command(AUT, 4);
|
||||
usleep(500000);
|
||||
ret = command_sequence(&command, 1, answer);
|
||||
|
||||
if (ret > 0) {
|
||||
upslogx(LOG_INFO, "Connected to UPS on %s with baudrate %d", port, pw_baud_rates[i].name);
|
||||
return;
|
||||
}
|
||||
|
||||
upsdebugx(2, "No response from UPS on %s with baudrate %d", port, pw_baud_rates[i].name);
|
||||
}
|
||||
|
||||
fatalx(EXIT_FAILURE, "Can't connect to the UPS on port %s!\n", port);
|
||||
}
|
||||
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
upsfd = ser_open(device_path);
|
||||
pw_comm_setup(device_path);
|
||||
}
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
/* free(dynamic_mem); */
|
||||
ser_close(upsfd, device_path);
|
||||
}
|
||||
|
||||
void upsdrv_reconnect(void)
|
||||
{
|
||||
}
|
||||
495
drivers/bcmxcp_usb.c
Normal file
495
drivers/bcmxcp_usb.c
Normal file
|
|
@ -0,0 +1,495 @@
|
|||
#include "main.h"
|
||||
#include "bcmxcp.h"
|
||||
#include "bcmxcp_io.h"
|
||||
#include "common.h"
|
||||
#include "usb-common.h"
|
||||
#include "timehead.h"
|
||||
#include "nut_stdint.h" /* for uint16_t */
|
||||
#include <ctype.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <usb.h>
|
||||
|
||||
#define SUBDRIVER_NAME "USB communication subdriver"
|
||||
#define SUBDRIVER_VERSION "0.18"
|
||||
|
||||
/* communication driver description structure */
|
||||
upsdrv_info_t comm_upsdrv_info = {
|
||||
SUBDRIVER_NAME,
|
||||
SUBDRIVER_VERSION,
|
||||
NULL,
|
||||
0,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
/* Powerware */
|
||||
#define POWERWARE 0x0592
|
||||
|
||||
/* Phoenixtec Power Co., Ltd */
|
||||
#define PHOENIXTEC 0x06da
|
||||
|
||||
/* Hewlett Packard */
|
||||
#define HP_VENDORID 0x03f0
|
||||
|
||||
/* USB functions */
|
||||
usb_dev_handle *nutusb_open(const char *port);
|
||||
int nutusb_close(usb_dev_handle *dev_h, const char *port);
|
||||
/* unified failure reporting: call these often */
|
||||
void nutusb_comm_fail(const char *fmt, ...)
|
||||
__attribute__ ((__format__ (__printf__, 1, 2)));
|
||||
void nutusb_comm_good(void);
|
||||
/* function pointer, set depending on which device is used */
|
||||
int (*usb_set_descriptor)(usb_dev_handle *udev, unsigned char type,
|
||||
unsigned char index, void *buf, int size);
|
||||
|
||||
/* usb_set_descriptor() for Powerware devices */
|
||||
static int usb_set_powerware(usb_dev_handle *udev, unsigned char type, unsigned char index, void *buf, int size)
|
||||
{
|
||||
return usb_control_msg(udev, USB_ENDPOINT_OUT, USB_REQ_SET_DESCRIPTOR, (type << 8) + index, 0, buf, size, 1000);
|
||||
}
|
||||
|
||||
static void *powerware_ups(void) {
|
||||
usb_set_descriptor = &usb_set_powerware;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* usb_set_descriptor() for Phoenixtec devices */
|
||||
static int usb_set_phoenixtec(usb_dev_handle *udev, unsigned char type, unsigned char index, void *buf, int size)
|
||||
{
|
||||
return usb_control_msg(udev, 0x42, 0x0d, (0x00 << 8) + 0x0, 0, buf, size, 1000);
|
||||
}
|
||||
|
||||
static void *phoenixtec_ups(void) {
|
||||
usb_set_descriptor = &usb_set_phoenixtec;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* USB IDs device table */
|
||||
static usb_device_id_t pw_usb_device_table[] = {
|
||||
/* various models */
|
||||
{ USB_DEVICE(POWERWARE, 0x0002), &powerware_ups },
|
||||
|
||||
/* various models */
|
||||
{ USB_DEVICE(PHOENIXTEC, 0x0002), &phoenixtec_ups },
|
||||
|
||||
/* T500 */
|
||||
{ USB_DEVICE(HP_VENDORID, 0x1f01), &phoenixtec_ups },
|
||||
/* T750 */
|
||||
{ USB_DEVICE(HP_VENDORID, 0x1f02), &phoenixtec_ups },
|
||||
|
||||
/* Terminating entry */
|
||||
{ -1, -1, NULL }
|
||||
};
|
||||
|
||||
/* limit the amount of spew that goes in the syslog when we lose the UPS */
|
||||
#define USB_ERR_LIMIT 10 /* start limiting after 10 in a row */
|
||||
#define USB_ERR_RATE 10 /* then only print every 10th error */
|
||||
#define XCP_USB_TIMEOUT 5000
|
||||
|
||||
/* global variables */
|
||||
usb_dev_handle *upsdev = NULL;
|
||||
extern int exit_flag;
|
||||
static unsigned int comm_failures = 0;
|
||||
|
||||
/* Functions implementations */
|
||||
void send_read_command(unsigned char command)
|
||||
{
|
||||
unsigned char buf[4];
|
||||
|
||||
buf[0] = PW_COMMAND_START_BYTE;
|
||||
buf[1] = 0x01; /* data length */
|
||||
buf[2] = command; /* command to send */
|
||||
buf[3] = calc_checksum(buf); /* checksum */
|
||||
usb_set_descriptor(upsdev, USB_DT_STRING, 4, buf, 4); /* FIXME: Ignore error */
|
||||
}
|
||||
|
||||
void send_write_command(unsigned char *command, int command_length)
|
||||
{
|
||||
unsigned char sbuf[128];
|
||||
|
||||
/* Prepare the send buffer */
|
||||
sbuf[0] = PW_COMMAND_START_BYTE;
|
||||
sbuf[1] = (unsigned char)(command_length);
|
||||
memcpy(sbuf+2, command, command_length);
|
||||
command_length += 2;
|
||||
|
||||
/* Add checksum */
|
||||
sbuf[command_length] = calc_checksum(sbuf);
|
||||
command_length += 1;
|
||||
usb_set_descriptor(upsdev, USB_DT_STRING, 4, sbuf, command_length); /* FIXME: Ignore error */
|
||||
}
|
||||
|
||||
/* get the answer of a command from the ups. And check that the answer is for this command */
|
||||
int get_answer(unsigned char *data, unsigned char command)
|
||||
{
|
||||
unsigned char buf[1024], *my_buf = buf;
|
||||
int length, end_length, res, endblock, bytes_read, ellapsed_time;
|
||||
unsigned char block_number, sequence, seq_num;
|
||||
struct timeval start_time, now;
|
||||
|
||||
length = 1; /* non zero to enter the read loop */
|
||||
end_length = 0; /* total length of sequence(s), not counting header(s) */
|
||||
endblock = 0; /* signal the last sequence in the block */
|
||||
bytes_read = 0; /* total length of data read, including XCP header */
|
||||
res = 0;
|
||||
ellapsed_time = 0;
|
||||
seq_num = 1; /* current theoric sequence */
|
||||
|
||||
upsdebugx(1, "entering get_answer(%x)", command);
|
||||
|
||||
/* Store current time */
|
||||
gettimeofday(&start_time, NULL);
|
||||
|
||||
while ( (!endblock) && ((XCP_USB_TIMEOUT - ellapsed_time) > 0) ) {
|
||||
|
||||
/* Get (more) data if needed */
|
||||
if ((length - bytes_read) > 0) {
|
||||
res = usb_interrupt_read(upsdev, 0x81,
|
||||
(char *)&buf[bytes_read],
|
||||
(PW_ANSWER_MAX_SIZE - bytes_read),
|
||||
(XCP_USB_TIMEOUT - ellapsed_time));
|
||||
|
||||
/* Update time */
|
||||
gettimeofday(&now, NULL);
|
||||
ellapsed_time = (now.tv_sec - start_time.tv_sec)*1000 +
|
||||
(now.tv_usec - start_time.tv_usec)/1000;
|
||||
|
||||
/* Check libusb return value */
|
||||
if (res < 0)
|
||||
{
|
||||
/* Clear any possible endpoint stalls */
|
||||
usb_clear_halt(upsdev, 0x81);
|
||||
//continue; // FIXME: seems a break would be better!
|
||||
break;
|
||||
}
|
||||
|
||||
/* this seems to occur on XSlot USB card */
|
||||
if (res == 0)
|
||||
{
|
||||
/* FIXME: */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Else, we got some input bytes */
|
||||
bytes_read += res;
|
||||
upsdebug_hex(1, "get_answer", buf, bytes_read);
|
||||
}
|
||||
|
||||
/* Now validate XCP frame */
|
||||
/* Check header */
|
||||
if ( my_buf[0] != 0xAB ) {
|
||||
upsdebugx(2, "get_answer: wrong header");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* These validations seem not needed! */
|
||||
/* Read block number byte */
|
||||
block_number = my_buf[1];
|
||||
upsdebugx(1, "get_answer: block_number = %x", block_number);
|
||||
#if 0
|
||||
if (command <= 0x43) {
|
||||
if ((command - 0x30) != block_number){
|
||||
nutusb_comm_fail("Receive error (Request command): BLOCK: %x (instead of %x), COMMAND: %x!\n",
|
||||
block_number, (command - 0x30), command);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (command >= 0x89) {
|
||||
if ((command == 0xA0) && (block_number != 0x01)){
|
||||
nutusb_comm_fail("Receive error (Request command): BLOCK: %x (instead of 0x01), COMMAND: %x!\n", block_number, command);
|
||||
return -1;
|
||||
}
|
||||
else if ((command != 0xA0) && (block_number != 0x09)){
|
||||
nutusb_comm_fail("Receive error (Request command): BLOCK: %x (instead of 0x09), COMMAND: %x!\n", block_number, command);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
#endif /* if 0 */
|
||||
|
||||
/* Check data length byte (remove the header length) */
|
||||
length = my_buf[2];
|
||||
upsdebugx(3, "get_answer: data length = %d", length);
|
||||
if ((bytes_read - 5) < length) {
|
||||
upsdebugx(2, "get_answer: need to read %d more data", length - (bytes_read - 5));
|
||||
continue;
|
||||
}
|
||||
/* Check if Length conforms to XCP (121 for normal, 140 for Test mode) */
|
||||
/* Use the more generous length for testing */
|
||||
if (length > 140 ) {
|
||||
upsdebugx(2, "get_answer: bad length");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Test the Sequence # */
|
||||
sequence = my_buf[3];
|
||||
if ((sequence & PW_SEQ_MASK) != seq_num) {
|
||||
nutusb_comm_fail("get_answer: not the right sequence received %x!!!\n", (sequence & PW_SEQ_MASK));
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
upsdebugx(2, "get_answer: sequence number (%x) is ok", (sequence & PW_SEQ_MASK));
|
||||
}
|
||||
|
||||
/* Validate checksum */
|
||||
if (!checksum_test(my_buf)) {
|
||||
nutusb_comm_fail("get_answer: checksum error! ");
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
upsdebugx(2, "get_answer: checksum is ok");
|
||||
}
|
||||
|
||||
/* Check if it's the last sequence */
|
||||
if (sequence & PW_LAST_SEQ) {
|
||||
/* we're done receiving data */
|
||||
upsdebugx(2, "get_answer: all data received");
|
||||
endblock = 1;
|
||||
}
|
||||
else {
|
||||
seq_num++;
|
||||
}
|
||||
|
||||
/* copy the current valid XCP frame back */
|
||||
memcpy(data+end_length, my_buf+4, length);
|
||||
/* increment pointers to process the next sequence */
|
||||
end_length += length;
|
||||
my_buf += length + 5;
|
||||
}
|
||||
upsdebugx(4, "get_answer: exiting (len=%d)", end_length);
|
||||
return end_length;
|
||||
}
|
||||
|
||||
/* Sends a single command (length=1). and get the answer */
|
||||
int command_read_sequence(unsigned char command, unsigned char *data)
|
||||
{
|
||||
int bytes_read = 0;
|
||||
int retry = 0;
|
||||
|
||||
while ((bytes_read < 1) && (retry < 5)) {
|
||||
send_read_command(command);
|
||||
bytes_read = get_answer(data, command);
|
||||
retry++;
|
||||
}
|
||||
|
||||
if (bytes_read < 1) {
|
||||
nutusb_comm_fail("Error executing command");
|
||||
dstate_datastale();
|
||||
return -1;
|
||||
}
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
/* Sends a setup command (length > 1) */
|
||||
int command_write_sequence(unsigned char *command, int command_length, unsigned char *answer)
|
||||
{
|
||||
int bytes_read = 0;
|
||||
int retry = 0;
|
||||
|
||||
while ((bytes_read < 1) && (retry < 5)) {
|
||||
send_write_command(command, command_length);
|
||||
bytes_read = get_answer(answer, command[0]);
|
||||
retry ++;
|
||||
}
|
||||
|
||||
if (bytes_read < 1) {
|
||||
nutusb_comm_fail("Error executing command");
|
||||
dstate_datastale();
|
||||
return -1;
|
||||
}
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_comm_good()
|
||||
{
|
||||
nutusb_comm_good();
|
||||
}
|
||||
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
upsdev = nutusb_open("USB");
|
||||
}
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
upslogx(LOG_ERR, "CLOSING\n");
|
||||
nutusb_close(upsdev, "USB");
|
||||
}
|
||||
|
||||
void upsdrv_reconnect(void)
|
||||
{
|
||||
|
||||
upslogx(LOG_WARNING, "RECONNECT USB DEVICE\n");
|
||||
nutusb_close(upsdev, "USB");
|
||||
upsdev = NULL;
|
||||
sleep(3);
|
||||
upsdrv_initups();
|
||||
}
|
||||
|
||||
/* USB functions */
|
||||
static void nutusb_open_error(const char *port)
|
||||
{
|
||||
printf("Unable to find POWERWARE UPS device on USB bus (%s)\n\n", port);
|
||||
|
||||
printf("Things to try:\n\n");
|
||||
printf(" - Connect UPS device to USB bus\n\n");
|
||||
printf(" - Run this driver as another user (upsdrvctl -u or 'user=...' in ups.conf).\n");
|
||||
printf(" See upsdrvctl(8) and ups.conf(5).\n\n");
|
||||
|
||||
fatalx(EXIT_FAILURE, "Fatal error: unusable configuration");
|
||||
}
|
||||
|
||||
/* FIXME: this part of the opening can go into common... */
|
||||
static usb_dev_handle *open_powerware_usb()
|
||||
{
|
||||
struct usb_bus *busses = usb_get_busses();
|
||||
struct usb_bus *bus;
|
||||
|
||||
for (bus = busses; bus; bus = bus->next)
|
||||
{
|
||||
struct usb_device *dev;
|
||||
|
||||
for (dev = bus->devices; dev; dev = dev->next)
|
||||
{
|
||||
if (dev->descriptor.bDeviceClass != USB_CLASS_PER_INTERFACE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_usb_device_supported(pw_usb_device_table,
|
||||
dev->descriptor.idVendor, dev->descriptor.idProduct) == SUPPORTED) {
|
||||
return usb_open(dev);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
usb_dev_handle *nutusb_open(const char *port)
|
||||
{
|
||||
static int libusb_init = 0;
|
||||
int dev_claimed = 0;
|
||||
usb_dev_handle *dev_h = NULL;
|
||||
int retry;
|
||||
|
||||
if (!libusb_init)
|
||||
{
|
||||
/* Initialize Libusb */
|
||||
usb_init();
|
||||
libusb_init = 1;
|
||||
usb_find_busses();
|
||||
usb_find_devices();
|
||||
}
|
||||
|
||||
for (retry = 0; dev_h == NULL && retry < 32; retry++)
|
||||
{
|
||||
struct timespec t = {5, 0};
|
||||
|
||||
dev_h = open_powerware_usb();
|
||||
if (!dev_h) {
|
||||
upslogx(LOG_WARNING, "Can't open POWERWARE USB device, retrying ...");
|
||||
if (nanosleep(&t, NULL) < 0 && errno == EINTR)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dev_h)
|
||||
{
|
||||
upslogx(LOG_ERR, "Can't open POWERWARE USB device");
|
||||
goto errout;
|
||||
}
|
||||
else
|
||||
upsdebugx(1, "device %s opened successfully", usb_device(dev_h)->filename);
|
||||
|
||||
if (usb_claim_interface(dev_h, 0) < 0)
|
||||
{
|
||||
upslogx(LOG_ERR, "Can't claim POWERWARE USB interface: %s", usb_strerror());
|
||||
goto errout;
|
||||
}
|
||||
else
|
||||
dev_claimed = 1;
|
||||
/* FIXME: this part of the opening can go into common... up to here at least */
|
||||
|
||||
if (usb_clear_halt(dev_h, 0x81) < 0)
|
||||
{
|
||||
upslogx(LOG_ERR, "Can't reset POWERWARE USB endpoint: %s", usb_strerror());
|
||||
goto errout;
|
||||
}
|
||||
return dev_h;
|
||||
|
||||
errout:
|
||||
if (dev_h && dev_claimed)
|
||||
usb_release_interface(dev_h, 0);
|
||||
if (dev_h)
|
||||
usb_close(dev_h);
|
||||
|
||||
nutusb_open_error(port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* FIXME: this part can go into common... */
|
||||
int nutusb_close(usb_dev_handle *dev_h, const char *port)
|
||||
{
|
||||
if (dev_h)
|
||||
{
|
||||
usb_release_interface(dev_h, 0);
|
||||
return usb_close(dev_h);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void nutusb_comm_fail(const char *fmt, ...)
|
||||
{
|
||||
int ret;
|
||||
char why[SMALLBUF];
|
||||
va_list ap;
|
||||
|
||||
/* this means we're probably here because select was interrupted */
|
||||
if (exit_flag != 0)
|
||||
return; /* ignored, since we're about to exit anyway */
|
||||
|
||||
comm_failures++;
|
||||
|
||||
if ((comm_failures == USB_ERR_LIMIT) ||
|
||||
((comm_failures % USB_ERR_RATE) == 0))
|
||||
{
|
||||
upslogx(LOG_WARNING, "Warning: excessive comm failures, "
|
||||
"limiting error reporting");
|
||||
}
|
||||
|
||||
/* once it's past the limit, only log once every USB_ERR_LIMIT calls */
|
||||
if ((comm_failures > USB_ERR_LIMIT) &&
|
||||
((comm_failures % USB_ERR_LIMIT) != 0))
|
||||
return;
|
||||
|
||||
/* generic message if the caller hasn't elaborated */
|
||||
if (!fmt)
|
||||
{
|
||||
upslogx(LOG_WARNING, "Communications with UPS lost"
|
||||
" - check cabling");
|
||||
return;
|
||||
}
|
||||
|
||||
va_start(ap, fmt);
|
||||
ret = vsnprintf(why, sizeof(why), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
if ((ret < 1) || (ret >= (int) sizeof(why)))
|
||||
upslogx(LOG_WARNING, "usb_comm_fail: vsnprintf needed "
|
||||
"more than %d bytes", (int)sizeof(why));
|
||||
|
||||
upslogx(LOG_WARNING, "Communications with UPS lost: %s", why);
|
||||
}
|
||||
|
||||
void nutusb_comm_good(void)
|
||||
{
|
||||
if (comm_failures == 0)
|
||||
return;
|
||||
|
||||
upslogx(LOG_NOTICE, "Communications with UPS re-established");
|
||||
comm_failures = 0;
|
||||
}
|
||||
480
drivers/belkin-hid.c
Normal file
480
drivers/belkin-hid.c
Normal file
|
|
@ -0,0 +1,480 @@
|
|||
/* belkin-hid.h - data to monitor Belkin UPS Systems USB/HID devices with NUT
|
||||
*
|
||||
* Copyright (C)
|
||||
* 2003 - 2008 Arnaud Quette <arnaud.quette@free.fr>
|
||||
* 2005 Peter Selinger <selinger@users.sourceforge.net>
|
||||
*
|
||||
* Sponsored by MGE UPS SYSTEMS <http://www.mgeups.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "main.h" /* for getval() */
|
||||
#include "usbhid-ups.h"
|
||||
#include "belkin-hid.h"
|
||||
#include "usb-common.h"
|
||||
|
||||
#define BELKIN_HID_VERSION "Belkin HID 0.12"
|
||||
|
||||
/* Belkin */
|
||||
#define BELKIN_VENDORID 0x050d
|
||||
|
||||
/* Liebert */
|
||||
#define LIEBERT_VENDORID 0x10af
|
||||
|
||||
/* USB IDs device table */
|
||||
static usb_device_id_t belkin_usb_device_table[] = {
|
||||
/* F6C800-UNV */
|
||||
{ USB_DEVICE(BELKIN_VENDORID, 0x0980), NULL },
|
||||
/* F6C900-UNV */
|
||||
{ USB_DEVICE(BELKIN_VENDORID, 0x0900), NULL },
|
||||
/* F6C100-UNV */
|
||||
{ USB_DEVICE(BELKIN_VENDORID, 0x0910), NULL },
|
||||
/* F6C120-UNV */
|
||||
{ USB_DEVICE(BELKIN_VENDORID, 0x0912), NULL },
|
||||
/* F6C550-AVR */
|
||||
{ USB_DEVICE(BELKIN_VENDORID, 0x0551), NULL },
|
||||
/* F6C1250-TW-RK */
|
||||
{ USB_DEVICE(BELKIN_VENDORID, 0x0750), NULL },
|
||||
/* F6C1500-TW-RK */
|
||||
{ USB_DEVICE(BELKIN_VENDORID, 0x0751), NULL },
|
||||
/* F6H375-USB */
|
||||
{ USB_DEVICE(BELKIN_VENDORID, 0x0375), NULL },
|
||||
/* F6C1100-UNV, F6C1200-UNV */
|
||||
{ USB_DEVICE(BELKIN_VENDORID, 0x1100), NULL },
|
||||
|
||||
/* Liebert PowerSure PSA UPS */
|
||||
{ USB_DEVICE(LIEBERT_VENDORID, 0x0001), NULL },
|
||||
|
||||
/* Terminating entry */
|
||||
{ -1, -1, NULL }
|
||||
};
|
||||
|
||||
/* some conversion functions specific to Belkin */
|
||||
|
||||
/* returns statically allocated string - must not use it again before
|
||||
done with result! */
|
||||
static char *belkin_firmware_conversion_fun(double value)
|
||||
{
|
||||
static char buf[20];
|
||||
|
||||
snprintf(buf, sizeof(buf), "%ld", (long)value >> 4);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static info_lkp_t belkin_firmware_conversion[] = {
|
||||
{ 0, NULL, belkin_firmware_conversion_fun }
|
||||
};
|
||||
|
||||
static char *belkin_upstype_conversion_fun(double value)
|
||||
{
|
||||
switch ((long)value & 0x0f)
|
||||
{
|
||||
case 1:
|
||||
return "offline";
|
||||
case 2:
|
||||
return "line-interactive";
|
||||
case 3:
|
||||
return "simple online";
|
||||
case 4:
|
||||
return "simple offline";
|
||||
case 5:
|
||||
return "simple line-interactive";
|
||||
default:
|
||||
return "online";
|
||||
}
|
||||
}
|
||||
|
||||
static info_lkp_t belkin_upstype_conversion[] = {
|
||||
{ 0, NULL, belkin_upstype_conversion_fun }
|
||||
};
|
||||
|
||||
static char *belkin_sensitivity_conversion_fun(double value)
|
||||
{
|
||||
switch ((long)value)
|
||||
{
|
||||
case 1:
|
||||
return "reduced";
|
||||
case 2:
|
||||
return "low";
|
||||
default:
|
||||
return "normal";
|
||||
}
|
||||
}
|
||||
|
||||
static info_lkp_t belkin_sensitivity_conversion[] = {
|
||||
{ 0, NULL, belkin_sensitivity_conversion_fun }
|
||||
};
|
||||
|
||||
static info_lkp_t belkin_test_info[] = {
|
||||
{ 0, "No test initiated", NULL },
|
||||
{ 1, "Done and passed", NULL },
|
||||
{ 2, "Done and warning", NULL },
|
||||
{ 3, "Done and error", NULL },
|
||||
{ 4, "Aborted", NULL },
|
||||
{ 5, "In progress", NULL },
|
||||
{ 0, NULL, NULL }
|
||||
};
|
||||
|
||||
static char *belkin_overload_conversion_fun(double value)
|
||||
{
|
||||
if ((long)value & 0x0010) {
|
||||
return "overload";
|
||||
} else {
|
||||
return "!overload";
|
||||
}
|
||||
}
|
||||
|
||||
static info_lkp_t belkin_overload_conversion[] = {
|
||||
{ 0, NULL, belkin_overload_conversion_fun }
|
||||
};
|
||||
|
||||
static char *belkin_overheat_conversion_fun(double value)
|
||||
{
|
||||
if ((long)value & 0x0040) {
|
||||
return "overheat";
|
||||
} else {
|
||||
return "!overheat";
|
||||
}
|
||||
}
|
||||
|
||||
static info_lkp_t belkin_overheat_conversion[] = {
|
||||
{ 0, NULL, belkin_overheat_conversion_fun }
|
||||
};
|
||||
|
||||
static char *belkin_commfault_conversion_fun(double value)
|
||||
{
|
||||
if ((long)value & 0x0080) {
|
||||
return "commfault";
|
||||
} else {
|
||||
return "!commfault";
|
||||
}
|
||||
}
|
||||
|
||||
static info_lkp_t belkin_commfault_conversion[] = {
|
||||
{ 0, NULL, belkin_commfault_conversion_fun }
|
||||
};
|
||||
|
||||
static char *belkin_awaitingpower_conversion_fun(double value)
|
||||
{
|
||||
if ((long)value & 0x2000) {
|
||||
return "awaitingpower";
|
||||
} else {
|
||||
return "!awaitingpower";
|
||||
}
|
||||
}
|
||||
|
||||
static info_lkp_t belkin_awaitingpower_conversion[] = {
|
||||
{ 0, NULL, belkin_awaitingpower_conversion_fun }
|
||||
};
|
||||
|
||||
static char *belkin_online_conversion_fun(double value)
|
||||
{
|
||||
if ((long)value & 0x0001) {
|
||||
return "!online";
|
||||
} else {
|
||||
return "online";
|
||||
}
|
||||
}
|
||||
|
||||
static info_lkp_t belkin_online_conversion[] = {
|
||||
{ 0, NULL, belkin_online_conversion_fun }
|
||||
};
|
||||
|
||||
static char *belkin_lowbatt_conversion_fun(double value)
|
||||
{
|
||||
if ((long)value & 0x0004) {
|
||||
return "lowbatt";
|
||||
} else {
|
||||
return "!lowbatt";
|
||||
}
|
||||
}
|
||||
|
||||
static info_lkp_t belkin_lowbatt_conversion[] = {
|
||||
{ 0, NULL, belkin_lowbatt_conversion_fun }
|
||||
};
|
||||
|
||||
static char *belkin_depleted_conversion_fun(double value)
|
||||
{
|
||||
if ((long)value & 0x0040) {
|
||||
return "depleted";
|
||||
} else {
|
||||
return "!depleted";
|
||||
}
|
||||
}
|
||||
|
||||
static info_lkp_t belkin_depleted_conversion[] = {
|
||||
{ 0, NULL, belkin_depleted_conversion_fun }
|
||||
};
|
||||
|
||||
static char *belkin_replacebatt_conversion_fun(double value)
|
||||
{
|
||||
if ((long)value & 0x0080) {
|
||||
return "replacebatt";
|
||||
} else {
|
||||
return "!replacebatt";
|
||||
}
|
||||
}
|
||||
|
||||
static info_lkp_t belkin_replacebatt_conversion[] = {
|
||||
{ 0, NULL, belkin_replacebatt_conversion_fun }
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* Vendor-specific usage table */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
/* BELKIN usage table */
|
||||
/* Note: these seem to have been wrongly encoded by Belkin */
|
||||
/* Pages 84 to 88 are reserved for official HID definition! */
|
||||
static usage_lkp_t belkin_usage_lkp[] = {
|
||||
{ "BELKINConfig", 0x00860026 },
|
||||
{ "BELKINConfigVoltage", 0x00860040 }, /* (V) */
|
||||
{ "BELKINConfigFrequency", 0x00860042 }, /* (Hz) */
|
||||
{ "BELKINConfigApparentPower", 0x00860043 }, /* (VA) */
|
||||
{ "BELKINConfigBatteryVoltage", 0x00860044 }, /* (V) */
|
||||
{ "BELKINConfigOverloadTransfer", 0x00860045 }, /* (%) */
|
||||
{ "BELKINLowVoltageTransfer", 0x00860053 }, /* R/W (V) */
|
||||
{ "BELKINHighVoltageTransfer", 0x00860054 }, /* R/W (V)*/
|
||||
{ "BELKINLowVoltageTransferMax", 0x0086005b }, /* (V) */
|
||||
{ "BELKINLowVoltageTransferMin", 0x0086005c }, /* (V) */
|
||||
{ "BELKINHighVoltageTransferMax", 0x0086005d }, /* (V) */
|
||||
{ "BELKINHighVoltageTransferMin", 0x0086005e }, /* (V) */
|
||||
|
||||
{ "BELKINControls", 0x00860027 },
|
||||
{ "BELKINLoadOn", 0x00860050 }, /* R/W: write: 1=do action. Read: 0=none, 1=started, 2=in progress, 3=complete */
|
||||
{ "BELKINLoadOff", 0x00860051 }, /* R/W: ditto */
|
||||
{ "BELKINLoadToggle", 0x00860052 }, /* R/W: ditto */
|
||||
{ "BELKINDelayBeforeReboot", 0x00860055 }, /* R/W: write: 0=start shutdown using default delay. */
|
||||
{ "BELKINDelayBeforeStartup", 0x00860056 }, /* R/W (minutes) */
|
||||
{ "BELKINDelayBeforeShutdown", 0x00860057 }, /* R/W (seconds) */
|
||||
{ "BELKINTest", 0x00860058 }, /* R/W: write: 0=no test, 1=quick test, 2=deep test, 3=abort test. Read: 0=no test, 1=passed, 2=warning, 3=error, 4=abort, 5=in progress */
|
||||
{ "BELKINAudibleAlarmControl", 0x0086005a }, /* R/W: 1=disabled, 2=enabled, 3=muted */
|
||||
|
||||
{ "BELKINDevice", 0x00860029 },
|
||||
{ "BELKINVoltageSensitivity", 0x00860074 }, /* R/W: 0=normal, 1=reduced, 2=low */
|
||||
{ "BELKINModelString", 0x00860075 },
|
||||
{ "BELKINModelStringOffset", 0x00860076 }, /* offset of Model name in Model String */
|
||||
{ "BELKINUPSType", 0x0086007c }, /* high nibble: firmware version. Low nibble: 0=online, 1=offline, 2=line-interactive, 3=simple online, 4=simple offline, 5=simple line-interactive */
|
||||
|
||||
{ "BELKINPowerState", 0x0086002a },
|
||||
{ "BELKINInput", 0x0086001a },
|
||||
{ "BELKINOutput", 0x0086001c },
|
||||
{ "BELKINBatterySystem", 0x00860010 },
|
||||
{ "BELKINVoltage", 0x00860030 }, /* (0.1 Volt) */
|
||||
{ "BELKINFrequency", 0x00860032 }, /* (0.1 Hz) */
|
||||
{ "BELKINPower", 0x00860034 }, /* (Watt) */
|
||||
{ "BELKINPercentLoad", 0x00860035 }, /* (%) */
|
||||
{ "BELKINTemperature", 0x00860036 }, /* (Celsius) */
|
||||
{ "BELKINCharge", 0x00860039 }, /* (%) */
|
||||
{ "BELKINRunTimeToEmpty", 0x0086006c }, /* (minutes) */
|
||||
|
||||
{ "BELKINStatus", 0x00860028 },
|
||||
{ "BELKINBatteryStatus", 0x00860022 }, /* 1 byte: bit2=low battery, bit4=charging, bit5=discharging, bit6=battery empty, bit7=replace battery */
|
||||
{ "BELKINPowerStatus", 0x00860021 }, /* 2 bytes: bit0=ac failure, bit4=overload, bit5=load is off, bit6=overheat, bit7=UPS fault, bit13=awaiting power, bit15=alarm status */
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
static usage_tables_t belkin_utab[] = {
|
||||
belkin_usage_lkp,
|
||||
hid_usage_lkp,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* HID2NUT lookup table */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
static hid_info_t belkin_hid2nut[] = {
|
||||
|
||||
/* interpreted Belkin variables */
|
||||
{ "battery.charge", 0, 0, "UPS.BELKINBatterySystem.BELKINCharge", NULL, "%.0f", 0, NULL },
|
||||
/* { "battery.charge.broken", 0, 0, "UPS.PowerSummary.RemainingCapacity", NULL, "%.0f", 0, NULL }, */
|
||||
{ "battery.charge.low", 0, 0, "UPS.PowerSummary.RemainingCapacityLimit", NULL, "%.0f", 0, NULL },
|
||||
{ "battery.charge.warning", 0, 0, "UPS.PowerSummary.WarningCapacityLimit", NULL, "%.0f", 0, NULL }, /* Read only */
|
||||
{ "battery.runtime", 0, 0, "UPS.PowerSummary.RunTimeToEmpty", NULL, "%.0f", 0, NULL },
|
||||
{ "battery.type", 0, 0, "UPS.PowerSummary.iDeviceChemistry", NULL, "%s", 0, stringid_conversion },
|
||||
{ "battery.voltage", 0, 0, "UPS.BELKINBatterySystem.BELKINVoltage", NULL, "%s", 0, divide_by_10_conversion },
|
||||
{ "battery.voltage.nominal", 0, 0, "UPS.BELKINConfig.BELKINConfigBatteryVoltage", NULL, "%.0f", 0, NULL },
|
||||
{ "input.frequency", 0, 0, "UPS.BELKINPowerState.BELKINInput.BELKINFrequency", NULL, "%s", 0, divide_by_10_conversion },
|
||||
{ "input.frequency.nominal", 0, 0, "UPS.BELKINConfig.BELKINConfigFrequency", NULL, "%.0f", 0, NULL },
|
||||
{ "input.sensitivity", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.BELKINDevice.BELKINVoltageSensitivity", NULL, "%s", 0, belkin_sensitivity_conversion },
|
||||
{ "input.transfer.high", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.BELKINConfig.BELKINHighVoltageTransfer", NULL, "%.0f", 0, NULL },
|
||||
{ "input.transfer.high.max", 0, 0, "UPS.BELKINConfig.BELKINHighVoltageTransferMax", NULL, "%.0f", 0, NULL },
|
||||
{ "input.transfer.high.min", 0, 0, "UPS.BELKINConfig.BELKINHighVoltageTransferMin", NULL, "%.0f", 0, NULL },
|
||||
{ "input.transfer.low", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.BELKINConfig.BELKINLowVoltageTransfer", NULL, "%.0f", 0, NULL },
|
||||
{ "input.transfer.low.max", 0, 0, "UPS.BELKINConfig.BELKINLowVoltageTransferMax", NULL, "%.0f", 0, NULL },
|
||||
{ "input.transfer.low.min", 0, 0, "UPS.BELKINConfig.BELKINLowVoltageTransferMin", NULL, "%.0f", 0, NULL },
|
||||
{ "input.voltage", 0, 0, "UPS.BELKINPowerState.BELKINInput.BELKINVoltage", NULL, "%s", 0, divide_by_10_conversion },
|
||||
{ "input.voltage.nominal", 0, 0, "UPS.BELKINConfig.BELKINConfigVoltage", NULL, "%.0f", 0, NULL },
|
||||
{ "output.frequency", 0, 0, "UPS.BELKINPowerState.BELKINOutput.BELKINFrequency", NULL, "%s", 0, divide_by_10_conversion },
|
||||
{ "output.voltage", 0, 0, "UPS.BELKINPowerState.BELKINOutput.BELKINVoltage", NULL, "%s", 0, divide_by_10_conversion },
|
||||
{ "ups.beeper.status", 0, 0, "UPS.BELKINControls.BELKINAudibleAlarmControl", NULL, "%s", 0, beeper_info },
|
||||
{ "ups.delay.start", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.BELKINControls.BELKINDelayBeforeStartup", NULL, DEFAULT_ONDELAY, HU_FLAG_ABSENT, NULL },
|
||||
{ "ups.delay.shutdown", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.BELKINControls.BELKINDelayBeforeShutdown", NULL, DEFAULT_OFFDELAY, HU_FLAG_ABSENT, NULL },
|
||||
{ "ups.timer.start", 0, 0, "UPS.BELKINControls.BELKINDelayBeforeStartup", NULL, "%.0f", HU_FLAG_QUICK_POLL, NULL },
|
||||
{ "ups.timer.shutdown", 0, 0, "UPS.BELKINControls.BELKINDelayBeforeShutdown", NULL, "%.0f", HU_FLAG_QUICK_POLL, NULL },
|
||||
{ "ups.timer.reboot", 0, 0, "UPS.BELKINControls.BELKINDelayBeforeReboot", NULL, "%.0f", HU_FLAG_QUICK_POLL, NULL },
|
||||
{ "ups.firmware", 0, 0, "UPS.BELKINDevice.BELKINUPSType", NULL, "%s", 0, belkin_firmware_conversion },
|
||||
{ "ups.load", 0, 0, "UPS.BELKINPowerState.BELKINOutput.BELKINPercentLoad", NULL, "%.0f", 0, NULL },
|
||||
{ "ups.load.high", 0, 0, "UPS.BELKINConfig.BELKINConfigOverloadTransfer", NULL, "%.0f", 0, NULL },
|
||||
{ "ups.mfr.date", 0, 0, "UPS.PowerSummary.ManufacturerDate", NULL, "%s", 0, date_conversion },
|
||||
{ "ups.power.nominal", 0, 0, "UPS.BELKINConfig.BELKINConfigApparentPower", NULL, "%.0f", 0, NULL },
|
||||
{ "ups.serial", 0, 0, "UPS.PowerSummary.iSerialNumber", NULL, "%s", 0, stringid_conversion },
|
||||
{ "ups.test.result", 0, 0, "UPS.BELKINControls.BELKINTest", NULL, "%s", 0, belkin_test_info },
|
||||
{ "ups.type", 0, 0, "UPS.BELKINDevice.BELKINUPSType", NULL, "%s", 0, belkin_upstype_conversion },
|
||||
|
||||
/* status */
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.Discharging", NULL, NULL, HU_FLAG_QUICK_POLL, discharging_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.Charging", NULL, NULL, HU_FLAG_QUICK_POLL, charging_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.ShutdownImminent", NULL, NULL, 0, shutdownimm_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.ACPresent", NULL, NULL, HU_FLAG_QUICK_POLL, online_info },
|
||||
/* { "BOOL", 0, 0, "UPS.PowerSummary.BelowRemainingCapacityLimit", NULL, "%s", 0, lowbatt_info }, broken! */
|
||||
{ "BOOL", 0, 0, "UPS.BELKINStatus.BELKINPowerStatus", NULL, NULL, 0, belkin_overload_conversion },
|
||||
{ "BOOL", 0, 0, "UPS.BELKINStatus.BELKINPowerStatus", NULL, NULL, 0, belkin_overheat_conversion },
|
||||
{ "BOOL", 0, 0, "UPS.BELKINStatus.BELKINPowerStatus", NULL, NULL, 0, belkin_commfault_conversion },
|
||||
{ "BOOL", 0, 0, "UPS.BELKINStatus.BELKINPowerStatus", NULL, NULL, 0, belkin_awaitingpower_conversion },
|
||||
{ "BOOL", 0, 0, "UPS.BELKINStatus.BELKINPowerStatus", NULL, NULL, HU_FLAG_QUICK_POLL, belkin_online_conversion },
|
||||
{ "BOOL", 0, 0, "UPS.BELKINStatus.BELKINBatteryStatus", NULL, NULL, HU_FLAG_QUICK_POLL, belkin_depleted_conversion },
|
||||
{ "BOOL", 0, 0, "UPS.BELKINStatus.BELKINBatteryStatus", NULL, NULL, 0, belkin_replacebatt_conversion },
|
||||
{ "BOOL", 0, 0, "UPS.BELKINStatus.BELKINBatteryStatus", NULL, NULL, HU_FLAG_QUICK_POLL, belkin_lowbatt_conversion },
|
||||
|
||||
/* instant commands. */
|
||||
/* split into subsets while waiting for extradata support
|
||||
* ie: test.battery.start quick
|
||||
*/
|
||||
{ "test.battery.start.quick", 0, 0, "UPS.BELKINControls.BELKINTest", NULL, "1", HU_TYPE_CMD, NULL },
|
||||
{ "test.battery.start.deep", 0, 0, "UPS.BELKINControls.BELKINTest", NULL, "2", HU_TYPE_CMD, NULL },
|
||||
{ "test.battery.stop", 0, 0, "UPS.BELKINControls.BELKINTest", NULL, "3", HU_TYPE_CMD, NULL },
|
||||
{ "beeper.on", 0, 0, "UPS.BELKINControls.BELKINAudibleAlarmControl", NULL, "2", HU_TYPE_CMD, NULL },
|
||||
{ "beeper.off", 0, 0, "UPS.BELKINControls.BELKINAudibleAlarmControl", NULL, "3", HU_TYPE_CMD, NULL },
|
||||
{ "beeper.disable", 0, 0, "UPS.BELKINControls.BELKINAudibleAlarmControl", NULL, "1", HU_TYPE_CMD, NULL },
|
||||
{ "beeper.enable", 0, 0, "UPS.BELKINControls.BELKINAudibleAlarmControl", NULL, "2", HU_TYPE_CMD, NULL },
|
||||
{ "beeper.mute", 0, 0, "UPS.BELKINControls.BELKINAudibleAlarmControl", NULL, "3", HU_TYPE_CMD, NULL },
|
||||
{ "load.off", 0, 0, "UPS.BELKINControls.BELKINDelayBeforeShutdown", NULL, "1", HU_TYPE_CMD, NULL },
|
||||
{ "load.on", 0, 0, "UPS.BELKINControls.BELKINDelayBeforeStartup", NULL, "1", HU_TYPE_CMD, NULL },
|
||||
{ "load.off.delay", 0, 0, "UPS.BELKINControls.BELKINDelayBeforeShutdown", NULL, DEFAULT_OFFDELAY, HU_TYPE_CMD, NULL },
|
||||
{ "load.on.delay", 0, 0, "UPS.BELKINControls.BELKINDelayBeforeStartup", NULL, DEFAULT_ONDELAY, HU_TYPE_CMD, NULL },
|
||||
{ "shutdown.stop", 0, 0, "UPS.BELKINControls.BELKINDelayBeforeShutdown", NULL, "-1", HU_TYPE_CMD, NULL },
|
||||
{ "shutdown.reboot", 0, 0, "UPS.BELKINControls.BELKINDelayBeforeReboot", NULL, "10", HU_TYPE_CMD, NULL },
|
||||
|
||||
/* Note: on my Belkin UPS, there is no way to implement
|
||||
shutdown.return or load.on, even though they should exist in
|
||||
principle. Hopefully other Belkin models will be better
|
||||
designed. Fixme: fill in the appropriate instant commands. For a
|
||||
more detailed description of the problem and a possible (but not
|
||||
yet implemented) workaround, see the belkinunv(8) man page.
|
||||
-PS 2005/08/28 */
|
||||
|
||||
/* end of structure. */
|
||||
{ NULL, 0, 0, NULL, NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
static char *belkin_format_model(HIDDevice_t *hd) {
|
||||
if ((hd->Product) && (strlen(hd->Product) > 0)) {
|
||||
return hd->Product;
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
static char *belkin_format_mfr(HIDDevice_t *hd) {
|
||||
char *mfr;
|
||||
mfr = hd->Vendor ? hd->Vendor : "Belkin";
|
||||
/* trim leading whitespace */
|
||||
while (*mfr == ' ') {
|
||||
mfr++;
|
||||
}
|
||||
if (strlen(mfr) == 0) {
|
||||
mfr = "Belkin";
|
||||
}
|
||||
return mfr;
|
||||
}
|
||||
|
||||
static char *belkin_format_serial(HIDDevice_t *hd) {
|
||||
char serial[64];
|
||||
|
||||
if (hd->Serial) {
|
||||
return hd->Serial;
|
||||
}
|
||||
|
||||
/* try UPS.PowerSummary.iSerialNumber */
|
||||
HIDGetItemString(udev, "UPS.PowerSummary.iSerialNumber",
|
||||
serial, sizeof(serial), belkin_utab);
|
||||
|
||||
if (strlen(serial) < 1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* free(hd->Serial); not needed, we already know it is NULL */
|
||||
hd->Serial = strdup(serial);
|
||||
return hd->Serial;
|
||||
}
|
||||
|
||||
/* this function allows the subdriver to "claim" a device: return 1 if
|
||||
* the device is supported by this subdriver, else 0. */
|
||||
static int belkin_claim(HIDDevice_t *hd)
|
||||
{
|
||||
int status = is_usb_device_supported(belkin_usb_device_table, hd->VendorID, hd->ProductID);
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case POSSIBLY_SUPPORTED:
|
||||
|
||||
switch (hd->VendorID)
|
||||
{
|
||||
case BELKIN_VENDORID:
|
||||
/* reject any known non-UPS */
|
||||
if (hd->ProductID == 0x0218) /* F5U218-MOB 4-Port USB Hub */
|
||||
return 0;
|
||||
|
||||
/* by default, reject, unless the productid option is given */
|
||||
if (getval("productid")) {
|
||||
return 1;
|
||||
}
|
||||
possibly_supported("Belkin", hd);
|
||||
return 0;
|
||||
|
||||
case LIEBERT_VENDORID:
|
||||
/* by default, reject, unless the productid option is given */
|
||||
if (getval("productid")) {
|
||||
return 1;
|
||||
}
|
||||
possibly_supported("Liebert", hd);
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
|
||||
case SUPPORTED:
|
||||
return 1;
|
||||
|
||||
case NOT_SUPPORTED:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
subdriver_t belkin_subdriver = {
|
||||
BELKIN_HID_VERSION,
|
||||
belkin_claim,
|
||||
belkin_utab,
|
||||
belkin_hid2nut,
|
||||
belkin_format_model,
|
||||
belkin_format_mfr,
|
||||
belkin_format_serial,
|
||||
};
|
||||
33
drivers/belkin-hid.h
Normal file
33
drivers/belkin-hid.h
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/* belkin-hid.h - data to monitor Belkin UPS Systems USB/HID devices with NUT
|
||||
*
|
||||
* Copyright (C)
|
||||
* 2003 - 2005 Arnaud Quette <arnaud.quette@free.fr>
|
||||
* 2005 Peter Selinger <selinger@users.sourceforge.net>
|
||||
*
|
||||
* Sponsored by MGE UPS SYSTEMS <http://www.mgeups.com>
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BELKIN_HID_H
|
||||
#define BELKIN_HID_H
|
||||
|
||||
#include "usbhid-ups.h"
|
||||
|
||||
extern subdriver_t belkin_subdriver;
|
||||
|
||||
#endif /* BELKIN_HID_H */
|
||||
446
drivers/belkin.c
Normal file
446
drivers/belkin.c
Normal file
|
|
@ -0,0 +1,446 @@
|
|||
/* belkin.c - model specific routines for Belkin Smart-UPS units.
|
||||
|
||||
Copyright (C) 2000 Marcus Müller <marcus@ebootis.de>
|
||||
|
||||
based on:
|
||||
|
||||
apcsmart.c - model specific routines for APC smart protocol units
|
||||
|
||||
Copyright (C) 1999 Russell Kroll <rkroll@exploits.org>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "serial.h"
|
||||
#include "belkin.h"
|
||||
|
||||
#define DRIVER_NAME "Belkin Smart protocol driver"
|
||||
#define DRIVER_VERSION "0.22"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Marcus Müller <marcus@ebootis.de>",
|
||||
DRV_STABLE,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static void send_belkin_command(char cmd, const char *subcmd, const char *data)
|
||||
{
|
||||
ser_send(upsfd, "~00%c%03d%s%s", cmd, (int)strlen(data) + 3, subcmd, data);
|
||||
}
|
||||
|
||||
static int init_communication(void)
|
||||
{
|
||||
int i;
|
||||
int res;
|
||||
char temp[SMALLBUF];
|
||||
|
||||
res = -1;
|
||||
for (i = 1; i <= 10 && res == -1; i++) {
|
||||
send_belkin_command(STATUS,MANUFACTURER,"");
|
||||
res = get_belkin_reply(temp);
|
||||
}
|
||||
|
||||
if (res == -1 || strcmp(temp,"BELKIN"))
|
||||
return res;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *get_belkin_field(const char *in, char *out, size_t outlen,
|
||||
size_t num)
|
||||
{
|
||||
size_t i, c = 1;
|
||||
char *ptr;
|
||||
|
||||
/* special case */
|
||||
if (num == 1) {
|
||||
snprintf(out, outlen, "%s", in);
|
||||
ptr = strchr(out, ';');
|
||||
|
||||
if (ptr)
|
||||
*ptr = '\0';
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
for (i = 0; i < strlen(in); i++) {
|
||||
if (in[i] == ';')
|
||||
c++;
|
||||
|
||||
if (c == num) {
|
||||
snprintf(out, outlen, "%s", &in[i + 1]);
|
||||
ptr = strchr(out, ';');
|
||||
|
||||
if (ptr)
|
||||
*ptr = '\0';
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int do_status(void)
|
||||
{
|
||||
char temp[SMALLBUF], st[SMALLBUF];
|
||||
int res;
|
||||
|
||||
send_belkin_command(STATUS,STAT_STATUS,"");
|
||||
res = get_belkin_reply(temp);
|
||||
if (res == -1) {
|
||||
dstate_datastale();
|
||||
return 0;
|
||||
}
|
||||
|
||||
status_init();
|
||||
|
||||
get_belkin_field(temp, st, sizeof(st), 6);
|
||||
if (*st == '1') {
|
||||
status_set("OFF");
|
||||
|
||||
} else { /* (OFF) and (OB | OL) are mutually exclusive */
|
||||
|
||||
get_belkin_field(temp, st, sizeof(st), 2);
|
||||
if (*st == '1') {
|
||||
status_set("OB");
|
||||
|
||||
send_belkin_command(STATUS,STAT_BATTERY,"");
|
||||
res = get_belkin_reply(temp);
|
||||
|
||||
if (res == -1) {
|
||||
dstate_datastale();
|
||||
return 0;
|
||||
}
|
||||
|
||||
get_belkin_field(temp, st, sizeof(st), 10);
|
||||
res = atoi(st);
|
||||
get_belkin_field(temp, st, sizeof(st), 2);
|
||||
|
||||
if (*st == '1' || res < LOW_BAT)
|
||||
status_set("LB"); /* low battery */
|
||||
}
|
||||
else
|
||||
status_set("OL"); /* on line */
|
||||
}
|
||||
|
||||
status_commit();
|
||||
dstate_dataok();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int do_broken_rat(char *buf)
|
||||
{
|
||||
int ret, cnt;
|
||||
char tmp[8];
|
||||
|
||||
usleep(25000);
|
||||
|
||||
ret = ser_get_buf_len(upsfd, (unsigned char *)tmp, 7, 3, 0);
|
||||
|
||||
if (ret != 7)
|
||||
return -1;
|
||||
|
||||
tmp[7] = '\0';
|
||||
cnt = atoi(tmp + 4);
|
||||
|
||||
if ((cnt < 1) || (cnt > 255))
|
||||
return -1;
|
||||
|
||||
usleep(5000 * cnt);
|
||||
|
||||
/* firmware 001 only sends 50 bytes instead of the proper 53 */
|
||||
if (cnt == 53)
|
||||
cnt = 50;
|
||||
|
||||
ret = ser_get_buf_len(upsfd, (unsigned char *)buf, 50, cnt, 0);
|
||||
buf[cnt] = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int init_ups_data(void)
|
||||
{
|
||||
int res;
|
||||
double low, high;
|
||||
char temp[SMALLBUF], st[SMALLBUF];
|
||||
|
||||
send_belkin_command(STATUS, MODEL, "");
|
||||
res = get_belkin_reply(temp);
|
||||
if (res == -1)
|
||||
return res;
|
||||
|
||||
dstate_setinfo("ups.model", "%s", temp);
|
||||
|
||||
send_belkin_command(STATUS, VERSION_CMD, "");
|
||||
res = get_belkin_reply(temp);
|
||||
if (res == -1)
|
||||
return res;
|
||||
|
||||
dstate_setinfo("ups.firmware", "%s", temp);
|
||||
|
||||
/* deal with stupid firmware that breaks RAT */
|
||||
|
||||
send_belkin_command(STATUS, RATING, "");
|
||||
|
||||
if (!strcmp(temp, "001"))
|
||||
res = do_broken_rat(temp);
|
||||
else
|
||||
res = get_belkin_reply(temp);
|
||||
|
||||
if (res > 0) {
|
||||
get_belkin_field(temp, st, sizeof(st), 8);
|
||||
low = atof(st) / 0.88;
|
||||
|
||||
get_belkin_field(temp, st, sizeof(st), 9);
|
||||
high = atof(st) * 0.88;
|
||||
|
||||
dstate_setinfo("input.transfer.low", "%03.1f", low);
|
||||
dstate_setinfo("input.transfer.high", "%03.1f", high);
|
||||
}
|
||||
|
||||
ser_flush_io(upsfd);
|
||||
|
||||
dstate_addcmd("load.off");
|
||||
dstate_addcmd("load.on");
|
||||
upsdrv_updateinfo();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* normal idle loop - keep up with the current state of the UPS */
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
int res;
|
||||
double val;
|
||||
char temp[SMALLBUF], st[SMALLBUF];
|
||||
|
||||
if (!do_status())
|
||||
return;
|
||||
|
||||
send_belkin_command(STATUS, STAT_INPUT, "");
|
||||
res = get_belkin_reply(temp);
|
||||
if (res == -1)
|
||||
return;
|
||||
|
||||
get_belkin_field(temp, st, sizeof(st), 3);
|
||||
val = atof(st) / 10;
|
||||
dstate_setinfo("input.voltage", "%05.1f", val);
|
||||
|
||||
get_belkin_field(temp, st, sizeof(st), 2);
|
||||
val = atof(st) / 10;
|
||||
dstate_setinfo("input.frequency", "%.1f", val);
|
||||
|
||||
send_belkin_command(STATUS,STAT_BATTERY, "");
|
||||
res = get_belkin_reply(temp);
|
||||
if (res == -1)
|
||||
return;
|
||||
|
||||
get_belkin_field(temp, st, sizeof(st), 10);
|
||||
val = atof(st);
|
||||
dstate_setinfo("battery.charge", "%03.0f", val);
|
||||
|
||||
get_belkin_field(temp, st, sizeof(st), 9);
|
||||
val = atof(st);
|
||||
dstate_setinfo("battery.temperature", "%03.0f", val);
|
||||
|
||||
get_belkin_field(temp, st, sizeof(st), 7);
|
||||
val = atof(st) / 10;
|
||||
dstate_setinfo("battery.voltage", "%4.1f", val);
|
||||
|
||||
get_belkin_field(temp, st, sizeof(st), 9);
|
||||
val = atof(st);
|
||||
dstate_setinfo("ups.temperature", "%03.0f", val);
|
||||
|
||||
send_belkin_command(STATUS, STAT_OUTPUT, "");
|
||||
res = get_belkin_reply(temp);
|
||||
if (res == -1)
|
||||
return;
|
||||
|
||||
get_belkin_field(temp, st, sizeof(st), 2);
|
||||
val = atof(st) / 10;
|
||||
dstate_setinfo("output.frequency", "%.1f", val);
|
||||
|
||||
get_belkin_field(temp, st, sizeof(st), 4);
|
||||
val = atof(st) / 10;
|
||||
dstate_setinfo("output.voltage", "%05.1f", val);
|
||||
|
||||
get_belkin_field(temp, st, sizeof(st), 7);
|
||||
val = atof(st);
|
||||
dstate_setinfo("ups.load", "%03.0f", val);
|
||||
}
|
||||
|
||||
static int get_belkin_reply(char *buf)
|
||||
{
|
||||
int ret, cnt;
|
||||
char tmp[8];
|
||||
|
||||
usleep(25000);
|
||||
|
||||
/* pull first 7 bytes to get data length - like ~00S004 */
|
||||
ret = ser_get_buf_len(upsfd, (unsigned char *)tmp, 7, 3, 0);
|
||||
|
||||
if (ret != 7) {
|
||||
ser_comm_fail("Initial read returned %d bytes", ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
tmp[7] = 0;
|
||||
cnt = atoi(tmp + 4);
|
||||
|
||||
if ((cnt < 1) || (cnt > 255))
|
||||
return -1;
|
||||
|
||||
/* give it time to respond to us */
|
||||
usleep(5000 * cnt);
|
||||
|
||||
ret = ser_get_buf_len(upsfd, (unsigned char *)buf, cnt, 3, 0);
|
||||
|
||||
buf[cnt] = 0;
|
||||
|
||||
if (ret != cnt) {
|
||||
ser_comm_fail("Second read returned %d bytes, expected %d",
|
||||
ret, cnt);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ser_comm_good();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* power down the attached load immediately */
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = init_communication();
|
||||
if (res == -1)
|
||||
printf("Detection failed. Trying a shutdown command anyway.\n");
|
||||
|
||||
/* tested on a F6C525-SER: this works when OL and OB */
|
||||
|
||||
/* shutdown type 2 (UPS system) */
|
||||
send_belkin_command(CONTROL, "SDT", "2");
|
||||
|
||||
/* SDR means "do SDT and SDA, then reboot after n minutes" */
|
||||
send_belkin_command(CONTROL, "SDR", "1");
|
||||
|
||||
printf("UPS should power off load in 5 seconds\n");
|
||||
|
||||
/* shutdown in 5 seconds */
|
||||
send_belkin_command(CONTROL, "SDA", "5");
|
||||
}
|
||||
|
||||
/* handle the "load.off" with some paranoia */
|
||||
static void do_off(void)
|
||||
{
|
||||
static time_t lastcmd = 0;
|
||||
time_t now, elapsed;
|
||||
#ifdef CONFIRM_DANGEROUS_COMMANDS
|
||||
time(&now);
|
||||
elapsed = now - lastcmd;
|
||||
|
||||
/* reset the timer every call - this means if you call it too *
|
||||
* early, then you have to wait MINCMDTIME again before sending #2 */
|
||||
lastcmd = now;
|
||||
|
||||
if ((elapsed < MINCMDTIME) || (elapsed > MAXCMDTIME)) {
|
||||
|
||||
/* FUTURE: tell the user (via upsd) to try it again */
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
upslogx(LOG_INFO, "Sending powerdown command to UPS\n");
|
||||
send_belkin_command(CONTROL,POWER_OFF,"1;1");
|
||||
usleep(1500000);
|
||||
send_belkin_command(CONTROL,POWER_OFF,"1;1");
|
||||
}
|
||||
|
||||
static int instcmd(const char *cmdname, const char *extra)
|
||||
{
|
||||
if (!strcasecmp(cmdname, "load.off")) {
|
||||
do_off();
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
if (!strcasecmp(cmdname, "load.on")) {
|
||||
send_belkin_command(CONTROL,POWER_ON,"1;1");
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
}
|
||||
|
||||
static void set_serialDTR0RTS1(void)
|
||||
{
|
||||
/* set DTR to low and RTS to high */
|
||||
ser_set_dtr(upsfd, 0);
|
||||
ser_set_rts(upsfd, 1);
|
||||
}
|
||||
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
}
|
||||
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
}
|
||||
|
||||
/* prep the serial port */
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
upsfd = ser_open(device_path);
|
||||
ser_set_speed(upsfd, device_path, B2400);
|
||||
|
||||
set_serialDTR0RTS1();
|
||||
|
||||
sleep(1);
|
||||
|
||||
ser_flush_io(upsfd);
|
||||
}
|
||||
|
||||
void upsdrv_initinfo(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = init_communication();
|
||||
if (res == -1) {
|
||||
fatalx(EXIT_FAILURE,
|
||||
"Unable to detect an Belkin Smart protocol UPS on port %s\n"
|
||||
"Check the cabling, port name or model name and try again", device_path
|
||||
);
|
||||
}
|
||||
|
||||
dstate_setinfo("ups.mfr", "BELKIN");
|
||||
|
||||
/* see what's out there */
|
||||
init_ups_data();
|
||||
|
||||
printf("Detected %s on %s\n", dstate_getinfo("ups.model"),
|
||||
device_path);
|
||||
|
||||
upsh.instcmd = instcmd;
|
||||
}
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
ser_close(upsfd, device_path);
|
||||
}
|
||||
53
drivers/belkin.h
Normal file
53
drivers/belkin.h
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/* belkin.h - serial commands for Belkin smart protocol units
|
||||
|
||||
Copyright (C) 2000 Marcus Müller <marcus@ebootis.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
#include "serial.h"
|
||||
#include "timehead.h"
|
||||
|
||||
#define STATUS 'P'
|
||||
#define CONTROL 'S'
|
||||
|
||||
#define MANUFACTURER "MNU"
|
||||
#define MODEL "MOD"
|
||||
#define VERSION_CMD "VER"
|
||||
#define RATING "RAT"
|
||||
#define STAT_INPUT "STI"
|
||||
#define STAT_OUTPUT "STO"
|
||||
#define STAT_BATTERY "STB"
|
||||
#define STAT_STATUS "STA"
|
||||
#define POWER_ON "RON"
|
||||
#define POWER_OFF "ROF"
|
||||
#define POWER_SDTYPE "SDT" /* shutdown type? */
|
||||
#define POWER_CYCLE "SDA" /* shutdown, then restore */
|
||||
|
||||
/* The UPS Status "low battery" comes up 10s before the UPS actually stops.
|
||||
Therefore a shutdown is done at this battery % */
|
||||
|
||||
#define LOW_BAT 20
|
||||
|
||||
/* dangerous instant commands must be reconfirmed within a 12 second window */
|
||||
#define CONFIRM_DANGEROUS_COMMANDS 1
|
||||
#define MINCMDTIME 3
|
||||
#define MAXCMDTIME 15
|
||||
|
||||
static int init_communication (void);
|
||||
static int init_ups_data (void);
|
||||
static int get_belkin_reply (char*);
|
||||
static void set_serialDTR0RTS1(void);
|
||||
1325
drivers/belkinunv.c
Normal file
1325
drivers/belkinunv.c
Normal file
File diff suppressed because it is too large
Load diff
766
drivers/bestfcom.c
Normal file
766
drivers/bestfcom.c
Normal file
|
|
@ -0,0 +1,766 @@
|
|||
/*
|
||||
bestfcom.c - model specific routines for Best Power F-Command ups models
|
||||
|
||||
This module is yet another rewritten mangle of the bestuferrups
|
||||
driver. This driver was written in an attempt to consolidate
|
||||
the various Best Fortress/FERRUPS modules that support the
|
||||
'f'-command set and provide support for more of these models.
|
||||
|
||||
Models tested with this new version:
|
||||
FortressII LI720
|
||||
FERRUPS FE2.1K
|
||||
FERRUPS FE4.3K
|
||||
FERRUPS FE18K
|
||||
FERRUPS FD4.3K
|
||||
|
||||
From bestuferrups.c :
|
||||
|
||||
This module is a 40% rewritten mangle of the bestfort module by
|
||||
Grant, which is a 75% rewritten mangle of the bestups module by
|
||||
Russell. It has no test battery command since my ME3100 does this
|
||||
by itself. (same as Grant's driver in this respect)
|
||||
|
||||
Copyright (C) 2002 Andreas Wrede <andreas@planix.com>
|
||||
Copyright (C) 2000 John Stone <johns@megapixel.com>
|
||||
Copyright (C) 2000 Grant Taylor <gtaylor@picante.com>
|
||||
Copyright (C) 1999 Russell Kroll <rkroll@exploits.org>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "serial.h"
|
||||
|
||||
#define DRIVER_NAME "Best Ferrups/Fortress driver"
|
||||
#define DRIVER_VERSION "0.12"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Andreas Wrede <andreas@planix.com>\n" \
|
||||
"John Stone <johns@megapixel.com>\n" \
|
||||
"Grant Taylor <gtaylor@picante.com>\n" \
|
||||
"Russell Kroll <rkroll@exploits.org>",
|
||||
DRV_EXPERIMENTAL,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
#define ENDCHAR '\r'
|
||||
#define IGNCHARS "\012"
|
||||
#define UPSDELAY 1
|
||||
|
||||
/* BEST Factory UPS Model Codes */
|
||||
#define FORTRESS 00
|
||||
#define PATRIOT 01
|
||||
#define FORTRESSII 02
|
||||
#define FERRUPS 03
|
||||
#define UNITY1 04
|
||||
|
||||
/* Internal driver UPS Model Codes */
|
||||
#define UNKNOWN 000
|
||||
#define FDxxxx 100
|
||||
#define FExxxx 200
|
||||
#define LIxxxx 300
|
||||
#define MExxxx 400
|
||||
#define MDxxxx 500
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* Blob of UPS configuration data from the formatconfig string */
|
||||
struct {
|
||||
int valid; /* set to 1 when this is filled in */
|
||||
|
||||
float idealbvolts; /* various interesting battery voltages */
|
||||
float fullvolts;
|
||||
float lowvolts;
|
||||
float emptyvolts;
|
||||
|
||||
int va; /* capacity of UPS in Volt-Amps */
|
||||
int watts; /* capacity of UPS in watts */
|
||||
int model; /* enumerated model type */
|
||||
int type; /* enumerated ups type*/
|
||||
|
||||
char name[16]; /* ups type name*/
|
||||
|
||||
} fc;
|
||||
|
||||
static int inverter_status;
|
||||
|
||||
/* Forward decls */
|
||||
|
||||
/* Set up all the funky shared memory stuff used to communicate with upsd */
|
||||
void upsdrv_initinfo (void)
|
||||
{
|
||||
/* now set up room for all future variables that are supported */
|
||||
|
||||
/*
|
||||
dstate_setinfo("driver.name", "%s", "bestfcom");
|
||||
*/
|
||||
dstate_setinfo("ups.mfr", "%s", "Best Power");
|
||||
switch(fc.model) {
|
||||
case MExxxx:
|
||||
dstate_setinfo("ups.model", "%s ME%d", fc.name, fc.va);
|
||||
break;
|
||||
case MDxxxx:
|
||||
dstate_setinfo("ups.model", "%s MD%d", fc.name, fc.va);
|
||||
break;
|
||||
case FDxxxx:
|
||||
dstate_setinfo("ups.model", "%s FD%d", fc.name, fc.va);
|
||||
break;
|
||||
case FExxxx:
|
||||
dstate_setinfo("ups.model", "%s FE%d", fc.name, fc.va);
|
||||
break;
|
||||
case LIxxxx:
|
||||
dstate_setinfo("ups.model", "%s LI%d", fc.name, fc.va);
|
||||
break;
|
||||
default:
|
||||
fatalx(EXIT_FAILURE, "Unknown model - oops!"); /* Will never get here, upsdrv_initups() will catch */
|
||||
}
|
||||
|
||||
dstate_setinfo("ups.power.nominal", "%d", fc.va);
|
||||
dstate_setinfo("ups.realpower.nominal", "%d", fc.watts);
|
||||
|
||||
/* Do we really need to waste time on this? */
|
||||
/*
|
||||
if (fc.model != FDxxxx) {
|
||||
if (execute("d 00\r", tmp, sizeof(tmp)) > 0)
|
||||
sscanf(tmp, "00 Time %8s", time);
|
||||
|
||||
if (execute("d 10\r", tmp, sizeof(tmp)) > 0)
|
||||
sscanf(tmp, "10 Date %8s", date);
|
||||
|
||||
dstate_setinfo("ups.time", "%s", time);
|
||||
dstate_setinfo("ups.date", "%s", date);
|
||||
}
|
||||
*/
|
||||
|
||||
dstate_setinfo("battery.voltage.nominal", "%05.2f", (double)fc.idealbvolts);
|
||||
|
||||
upsdebugx(1, "Best Power %s detected", dstate_getinfo("ups.model"));
|
||||
upsdebugx(1, "Battery voltages: %5.2f nominal, %5.2f full, %5.2f low, %5.2f empty",
|
||||
fc.idealbvolts,
|
||||
fc.fullvolts,
|
||||
fc.lowvolts,
|
||||
fc.emptyvolts);
|
||||
}
|
||||
|
||||
|
||||
/* atoi() without the freebie octal conversion */
|
||||
int bcd2i (const char *bcdstring, const int bcdlen)
|
||||
{
|
||||
int i, digit, total = 0, factor = 1;
|
||||
for (i = 1; i < bcdlen; i++)
|
||||
factor *= 10;
|
||||
for (i = 0; i < bcdlen; i++) {
|
||||
digit = bcdstring[i] - '0';
|
||||
if (digit > 9) {
|
||||
digit = 0;
|
||||
}
|
||||
total += digit * factor;
|
||||
factor /= 10;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
#define POLL_ALERT "{"
|
||||
static void alert_handler(char ch)
|
||||
{
|
||||
char buf[256];
|
||||
|
||||
/* Received an Inverter status alarm :
|
||||
* "\r\n{Inverter: On}\r\n=>"
|
||||
* Try to flush the message
|
||||
*/
|
||||
ser_get_line(upsfd, buf, sizeof(buf), '\012', "", 0, 20000);
|
||||
}
|
||||
|
||||
/* Debugging display from kermit:
|
||||
----------------------------------------------------
|
||||
time^M^M^JFeb 20, 22:13:32^M^J^M^J=>id^M^JUnit ID "ME3.1K12345"^M^J^M^J=>
|
||||
----------------------------------------------------
|
||||
*/
|
||||
static int execute(const char *cmd, char *result, int resultsize)
|
||||
{
|
||||
int ret;
|
||||
char buf[256];
|
||||
unsigned char ch;
|
||||
|
||||
/* Check for the Inverter status alarm if pending :
|
||||
* "\r\n{Inverter: On}\r\n=>"
|
||||
*/
|
||||
ser_get_line_alert(upsfd, buf, sizeof(buf), '\012', "",
|
||||
POLL_ALERT, alert_handler, 0, 20000);
|
||||
|
||||
ser_send(upsfd, "%s", cmd);
|
||||
|
||||
/* Give the UPS some time to chew on what we just sent */
|
||||
usleep(50000);
|
||||
|
||||
/* delete command echo up to \012 but no further */
|
||||
for (ch = '\0'; ch != '\012'; ser_get_char(upsfd, &ch, 0, 10000));
|
||||
|
||||
/* get command response */
|
||||
ret = ser_get_line(upsfd, result, resultsize, '\015', "\012", 3, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
format command response -> 80 chars
|
||||
chrg line status
|
||||
||alrm ||
|
||||
Date Invtr |12| error
|
||||
| ||Time| || |Vi||Vo| |Io|| VA | |Vb||Hz||rt| || |vr|CS
|
||||
011314581801000000010000011601160000002300026600000265600000190000000000E00106E6\r
|
||||
01161706430100010001000002040121000000980011890000057959980001002200000064080727\r
|
||||
011800364801000100010000021301200000003100037100001343599803060024000000680807A0\r
|
||||
0121022719010001000100000208011900190000000000000005676001082200350000000006101A\r
|
||||
00000000000100000000000002370236000000220005190000026850000009002600000000030161\r
|
||||
0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8
|
||||
0 5 0 5 0 5 0 5 0 5 0 5 0 5 0 5 0
|
||||
|
||||
Above f-responses listed in this order:
|
||||
FortressII LI720
|
||||
FERRUPS FE4.3K
|
||||
FERRUPS FE18K
|
||||
FERRUPS FD4.3K
|
||||
Fortress ?????? (from Holger's old Best Fortress notes)
|
||||
*/
|
||||
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
char fstring[512];
|
||||
|
||||
if (! fc.valid) {
|
||||
upsdebugx(1, "upsupdate run before ups_ident() read ups config");
|
||||
assert(0);
|
||||
}
|
||||
|
||||
if (execute("f\r", fstring, sizeof(fstring)) >= 80) {
|
||||
int inverter=0, charger=0, vin=0, vout=0, btimeleft=0, linestat=0,
|
||||
alstat=0, vaout=0;
|
||||
|
||||
double ampsout=0.0, vbatt=0.0, battpercent=0.0, loadpercent=0.0,
|
||||
upstemp=0.0, acfreq=0.0;
|
||||
|
||||
char date[9], time[9], tmp[32];
|
||||
|
||||
upsdebugx(3, "f response: %d %s", (int)strlen(fstring), fstring);
|
||||
|
||||
date[0]='\0';
|
||||
time[0]='\0';
|
||||
|
||||
/* Inverter status. 0=off 1=on */
|
||||
inverter = bcd2i(&fstring[16], 2);
|
||||
|
||||
/* Charger status. 0=off 1=on */
|
||||
charger = bcd2i(&fstring[18], 2);
|
||||
|
||||
/* Input Voltage. integer number */
|
||||
vin = bcd2i(&fstring[24], 4);
|
||||
|
||||
/* Output Voltage. integer number */
|
||||
vout = bcd2i(&fstring[28], 4);
|
||||
|
||||
/* Battery voltage. int times 10 */
|
||||
vbatt = ((double)bcd2i(&fstring[50], 4) / 10.0);
|
||||
|
||||
/* Alarm status reg 1. Bitmask */
|
||||
alstat = bcd2i(&fstring[20], 2);
|
||||
|
||||
/* Alarm status reg 2. Bitmask */
|
||||
alstat = alstat | (bcd2i(&fstring[22], 2) << 8);
|
||||
|
||||
/* AC line frequency */
|
||||
acfreq = ((double)bcd2i(&fstring[54], 4) / 100.0);
|
||||
|
||||
/* Runtime remaining (UPS reports minutes) */
|
||||
btimeleft = bcd2i(&fstring[58], 4) * 60;
|
||||
|
||||
if (fc.model != FDxxxx) {
|
||||
/* Iout. int times 10 */
|
||||
ampsout = ((double)bcd2i(&fstring[36], 4) / 10.0);
|
||||
|
||||
/* Volt-amps out. int */
|
||||
vaout = bcd2i(&fstring[40], 6);
|
||||
|
||||
/* Line status. Bitmask */
|
||||
linestat = bcd2i(&fstring[72], 2);
|
||||
|
||||
}
|
||||
|
||||
if (fc.model != LIxxxx) {
|
||||
upstemp = (double) bcd2i(&fstring[62], 4);
|
||||
}
|
||||
|
||||
/* Percent Load */
|
||||
switch(fc.model) {
|
||||
case LIxxxx:
|
||||
case FDxxxx:
|
||||
case FExxxx:
|
||||
case MExxxx:
|
||||
if (execute("d 16\r", tmp, sizeof(tmp)) > 0) {
|
||||
int l;
|
||||
sscanf(tmp, "16 FullLoad%% %d", &l);
|
||||
loadpercent = (double) l;
|
||||
}
|
||||
break;
|
||||
case MDxxxx:
|
||||
if (execute("d 22\r", tmp, sizeof(tmp)) > 0) {
|
||||
int l;
|
||||
sscanf(tmp, "22 FullLoad%% %d", &l);
|
||||
loadpercent = (double) l;
|
||||
}
|
||||
break;
|
||||
default: /* Will never happen, caught in upsdrv_initups() */
|
||||
fatalx(EXIT_FAILURE, "Unknown model in upsdrv_updateinfo()");
|
||||
}
|
||||
|
||||
/* Compute battery percent left based on battery voltages. */
|
||||
battpercent = ((vbatt - fc.emptyvolts)
|
||||
/ (fc.fullvolts - fc.emptyvolts) * 100.0);
|
||||
if (battpercent < 0.0)
|
||||
battpercent = 0.0;
|
||||
else if (battpercent > 100.0)
|
||||
battpercent = 100.0;
|
||||
|
||||
/* Compute status string */
|
||||
{
|
||||
int lowbatt, lowvolts, overload, replacebatt, boosting, trimming;
|
||||
|
||||
lowbatt = alstat & (1<<1);
|
||||
overload = alstat & (1<<6);
|
||||
replacebatt = alstat & (1<<10);
|
||||
|
||||
boosting = inverter && (linestat & (1<<2)) && (vin < 115);
|
||||
trimming = inverter && (linestat & (1<<2)) && (vin > 115);
|
||||
|
||||
/* status bits can be unreliable, so try to help it out */
|
||||
lowvolts = (vbatt <= fc.lowvolts);
|
||||
|
||||
status_init();
|
||||
|
||||
if (inverter) {
|
||||
if (inverter_status < 1) {
|
||||
upsdebugx(1, "Inverter On, charger: %d battery time left: %d",
|
||||
charger, btimeleft);
|
||||
}
|
||||
inverter_status = 1;
|
||||
status_set("OB");
|
||||
} else {
|
||||
if (inverter_status) {
|
||||
upsdebugx(1, "Inverter Off, charger: %d battery time left: %d",
|
||||
charger, btimeleft);
|
||||
}
|
||||
inverter_status = 0;
|
||||
status_set("OL");
|
||||
}
|
||||
|
||||
if (lowbatt | lowvolts)
|
||||
status_set("LB");
|
||||
|
||||
if (trimming)
|
||||
status_set("TRIM");
|
||||
|
||||
if (boosting)
|
||||
status_set("BOOST");
|
||||
|
||||
if (replacebatt)
|
||||
status_set("RB");
|
||||
|
||||
if (overload)
|
||||
status_set("OVER");
|
||||
|
||||
status_commit();
|
||||
}
|
||||
|
||||
upsdebugx(2,
|
||||
"Poll: inverter %d charger %d vin %d vout %d vaout %d btimeleft %d",
|
||||
inverter, charger, vin, vout, vaout, btimeleft);
|
||||
upsdebugx(2,
|
||||
" vbatt %5.1f batpcnt %5.1f loadpcnt %5.1f upstemp %5.1f ampsout %5.1f acfreq %5.2f",
|
||||
vbatt, battpercent, loadpercent, upstemp, ampsout, acfreq);
|
||||
|
||||
/* Stuff information into info structures */
|
||||
dstate_setinfo("input.voltage", "%05.1f", (double)vin);
|
||||
dstate_setinfo("input.frequency", "%05.2f", acfreq);
|
||||
dstate_setinfo("output.voltage", "%05.1f", (double)vout);
|
||||
dstate_setinfo("output.current", "%04.1f", ampsout);
|
||||
dstate_setinfo("battery.charge", "%02.1f", battpercent);
|
||||
dstate_setinfo("battery.voltage", "%02.1f", vbatt);
|
||||
dstate_setinfo("battery.runtime", "%d", btimeleft);
|
||||
dstate_setinfo("ups.load", "%02.1f", loadpercent);
|
||||
if (vaout)
|
||||
dstate_setinfo("ups.power", "%d", vaout);
|
||||
if (upstemp)
|
||||
dstate_setinfo("ups.temperature", "%05.1f", (double)upstemp);
|
||||
|
||||
dstate_dataok();
|
||||
|
||||
} else {
|
||||
|
||||
upsdebugx(1, "failed f response. strlen: %d", (int)strlen(fstring));
|
||||
dstate_datastale();
|
||||
|
||||
} /* if (execute("f\r", fstring, sizeof(fstring)) >= 80) */
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
static void ups_sync(void)
|
||||
{
|
||||
char buf[256];
|
||||
|
||||
/* A bit better sanity might be good here. As is, we expect the
|
||||
human to observe the time being totally not a time. */
|
||||
|
||||
if (execute("time\r", buf, sizeof(buf)) > 0) {
|
||||
upsdebugx(1, "UPS Time: %s", buf);
|
||||
} else {
|
||||
fatalx(EXIT_FAILURE, "Error connecting to UPS.");
|
||||
}
|
||||
/* old Ferrups prompt for new time so send a blank line */
|
||||
execute("\r", buf, sizeof(buf));
|
||||
ser_get_line(upsfd, buf, sizeof(buf), '>', "\012", 3, 0);
|
||||
}
|
||||
|
||||
/* power down the attached load immediately */
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
/* NB: hard-wired password */
|
||||
ser_send(upsfd, "pw377\r");
|
||||
ser_send(upsfd, "off 1 a\r"); /* power off in 1 second and restart when line power returns */
|
||||
}
|
||||
|
||||
/* list flags and values that you want to receive via -x */
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
}
|
||||
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
}
|
||||
|
||||
static void sync_serial(void) {
|
||||
char buffer[10];
|
||||
|
||||
ser_flush_in(upsfd, "", 1);
|
||||
|
||||
ser_send(upsfd, "\r");
|
||||
sleep(UPSDELAY);
|
||||
ser_get_line(upsfd, buffer, sizeof(buffer), '\r', "\012", 3, 0);
|
||||
ser_get_line(upsfd, buffer, sizeof(buffer), ENDCHAR, IGNCHARS, 3, 0);
|
||||
|
||||
while (ser_get_line(upsfd, buffer, sizeof(buffer), '>', "\012", 3, 0) <= 0) {
|
||||
printf(".");
|
||||
ser_send(upsfd, "\r");
|
||||
sleep(UPSDELAY);
|
||||
}
|
||||
}
|
||||
|
||||
/* Begin code stolen from bestups.c */
|
||||
static void setup_serial(void)
|
||||
{
|
||||
struct termios tio;
|
||||
|
||||
if (tcgetattr(upsfd, &tio) == -1)
|
||||
fatal_with_errno(EXIT_FAILURE, "tcgetattr");
|
||||
|
||||
tio.c_iflag = IXON | IXOFF;
|
||||
tio.c_oflag = 0;
|
||||
tio.c_cflag = (CS8 | CREAD | HUPCL | CLOCAL);
|
||||
tio.c_lflag = 0;
|
||||
tio.c_cc[VMIN] = 1;
|
||||
tio.c_cc[VTIME] = 0;
|
||||
|
||||
#ifdef HAVE_CFSETISPEED
|
||||
cfsetispeed(&tio, B1200); /* baud change here */
|
||||
cfsetospeed(&tio, B1200);
|
||||
#else
|
||||
#error This system lacks cfsetispeed() and has no other means to set the speed
|
||||
#endif
|
||||
|
||||
if (tcsetattr(upsfd, TCSANOW, &tio) == -1)
|
||||
fatal_with_errno(EXIT_FAILURE, "tcsetattr");
|
||||
/* end code stolen from bestups.c */
|
||||
|
||||
sync_serial();
|
||||
}
|
||||
|
||||
/*
|
||||
These models don't support the formatconfig (fc) command so use
|
||||
the identify command.
|
||||
|
||||
"id\r" returns :
|
||||
|
||||
FERRUPS Uninterruptible Power System
|
||||
By Best Power Technology, Inc.
|
||||
Route 1 Highway 80 / P.O. Box 280
|
||||
Necedah, WI 54646 USA
|
||||
Sales: (800) 356-5794
|
||||
Service: (800) 356-5737
|
||||
FAX: (608) 565-2221
|
||||
|
||||
Copyright (C) 1993, 1994, 1995 Best Power Technology, Inc.
|
||||
|
||||
Model: FE4.3KVA
|
||||
Unit ID: FE4.3K02376
|
||||
Serial #: FE4.3K02376
|
||||
Version: 8.07
|
||||
Released: 08/01/1995
|
||||
*/
|
||||
|
||||
void upsdrv_init_nofc()
|
||||
{
|
||||
char tmp[256], rstring[1024];
|
||||
|
||||
/* This is a Best UPS
|
||||
* Set initial values for old Fortress???
|
||||
*/
|
||||
|
||||
/* Attempt the id command */
|
||||
ser_send(upsfd, "id\r");
|
||||
|
||||
/* prevent upsrecv from timing out */
|
||||
sleep(UPSDELAY);
|
||||
|
||||
ser_get_line(upsfd, rstring, sizeof(rstring), '>', "", 3, 0);
|
||||
|
||||
rstring[sizeof(rstring) - 1] = '\0';
|
||||
upsdebugx(2, "id response: %s", rstring);
|
||||
|
||||
/* Better way to identify this unit is using "d 15\r", which results in
|
||||
"15 M# MD1KVA", "id\r" yields "Unit ID "C1K03588"" */
|
||||
if (strstr(rstring, "Unit ID \"C1K")){
|
||||
fc.model = MDxxxx;
|
||||
snprintf(fc.name, sizeof(fc.name), "%s", "Micro Ferrups");
|
||||
|
||||
/* Determine load rating by Unit Id? */
|
||||
if (strstr(rstring, "Unit ID \"C1K")) {
|
||||
fc.va = 1100;
|
||||
fc.watts = 770; /* Approximate, based on 0.7 power factor */
|
||||
}
|
||||
} else
|
||||
if (strstr(rstring, "Unit ID \"ME")){
|
||||
fc.model = MExxxx;
|
||||
snprintf(fc.name, sizeof(fc.name), "%s", "Micro Ferrups");
|
||||
|
||||
/* Determine load rating by Unit Id? */
|
||||
if (strstr(rstring, "Unit ID \"ME3.1K")) {
|
||||
fc.va = 3100;
|
||||
fc.watts = 2200;
|
||||
}
|
||||
} else
|
||||
if (strstr(rstring, "Unit ID \"FD")){
|
||||
fc.model = FDxxxx;
|
||||
snprintf(fc.name, sizeof(fc.name), "%s", "Ferrups");
|
||||
|
||||
/* Determine load rating by Unit Id? */
|
||||
if (strstr(rstring, "Unit ID \"FD4.3K")) {
|
||||
fc.va = 4300;
|
||||
fc.watts = 3000;
|
||||
}
|
||||
} else
|
||||
if (strstr(rstring, "Model: FE")
|
||||
|| strstr(rstring, "Model: FE")){
|
||||
fc.model = FExxxx;
|
||||
fc.type = FERRUPS;
|
||||
snprintf(fc.name, sizeof(fc.name), "%s", "Ferrups");
|
||||
} else
|
||||
if (strlen(rstring) < 300 ) {
|
||||
/* How does the old Fortress respond to this? */
|
||||
upsdebugx(2, "Old Best Fortress???");
|
||||
/* fc.model = FORTRESS; */
|
||||
}
|
||||
|
||||
if (fc.model == UNKNOWN) {
|
||||
fatalx(EXIT_FAILURE, "Unknown model %s in upsdrv_init_nofc()", rstring);
|
||||
}
|
||||
|
||||
switch(fc.model) {
|
||||
case MExxxx:
|
||||
case MDxxxx:
|
||||
case FDxxxx:
|
||||
/* determine shutdown battery voltage */
|
||||
if (execute("d 27\r", tmp, sizeof(tmp)) > 0) {
|
||||
sscanf(tmp, "27 LowBatt %f", &fc.emptyvolts);
|
||||
}
|
||||
/* determine near low battery voltage */
|
||||
if (execute("d 30\r", tmp, sizeof(tmp)) > 0) {
|
||||
sscanf(tmp, "30 NLBatt %f", &fc.lowvolts);
|
||||
}
|
||||
/* determine fully charged battery voltage */
|
||||
if (execute("d 28\r", tmp, sizeof(tmp)) > 0) {
|
||||
sscanf(tmp, "28 Hi Batt %f", &fc.fullvolts);
|
||||
}
|
||||
fc.fullvolts = 13.70;
|
||||
/* determine "ideal" voltage by a guess */
|
||||
fc.idealbvolts = ((fc.fullvolts - fc.emptyvolts) * 0.7) + fc.emptyvolts;
|
||||
break;
|
||||
case FExxxx:
|
||||
if (execute("d 45\r", tmp, sizeof(tmp)) > 0) {
|
||||
sscanf(tmp, "45 RatedVA %d", &fc.va); /* 4300 */
|
||||
}
|
||||
if (execute("d 46\r", tmp, sizeof(tmp)) > 0) {
|
||||
sscanf(tmp, "46 RatedW %d", &fc.watts); /* 3000 */
|
||||
}
|
||||
if (execute("d 65\r", tmp, sizeof(tmp)) > 0) {
|
||||
sscanf(tmp, "65 LoBatV %f", &fc.emptyvolts); /* 41.00 */
|
||||
}
|
||||
if (execute("d 66\r", tmp, sizeof(tmp)) > 0) {
|
||||
sscanf(tmp, "66 NLBatV %f", &fc.lowvolts); /* 44.00 */
|
||||
}
|
||||
if (execute("d 67\r", tmp, sizeof(tmp)) > 0) {
|
||||
sscanf(tmp, "67 HiBatV %f", &fc.fullvolts); /* 59.60 */
|
||||
}
|
||||
fc.idealbvolts = ((fc.fullvolts - fc.emptyvolts) * 0.7) + fc.emptyvolts;
|
||||
if (fc.va < 1.0) {
|
||||
fatalx(EXIT_FAILURE, "Error determining Ferrups UPS rating.");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
fatalx(EXIT_FAILURE, "Unknown model %s in upsdrv_init_nofc()", rstring);
|
||||
break;
|
||||
}
|
||||
fc.valid = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
These models support the formatconfig (fc) command
|
||||
|
||||
formatconfig (fc) response is a one-line packed string starting with $
|
||||
Model Wt rat Vout VHi FrLo BatVN BatNLo LRuntime Model
|
||||
| | | | | | | | | | | | | | | | | | E
|
||||
rev VA rat Vin VLo FrN FrHi BatHi BatLo Opt O
|
||||
|| | | | | | | | | | | | | | | | T
|
||||
$010207010600720004701201200911446000570063000240028802150190003??????\LI????VA\\|
|
||||
$010207010600720004701201200911446000570063000240028802150190003??????\LI720VU\LI720VU18112\|
|
||||
0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8
|
||||
0 5 0 5 0 5 0 5 0 5 0 5 0 5 0 5 0
|
||||
|
||||
Model: [0,1] => 00 = unk, 01 = Patriot/SPS, 02 = FortressII, 03 = Ferrups, 04 = Unity/1
|
||||
[2,3] => 00 = LI520, 01 = LI720, 02 = LI1020, 03 = LI1420, 07 = ???
|
||||
*/
|
||||
|
||||
void upsdrv_init_fc(const char *fcstring)
|
||||
{
|
||||
char tmp[256];
|
||||
|
||||
upsdebugx(3, "fc response: %d %s", (int)strlen(fcstring), fcstring);
|
||||
|
||||
/* Obtain Model */
|
||||
if (memcmp(fcstring, "$", 1)) {
|
||||
fatalx(EXIT_FAILURE, "Bad response from formatconfig command in upsdrv_init_fc()");
|
||||
}
|
||||
if (memcmp(fcstring+3, "00", 2) == 0) {
|
||||
fatalx(EXIT_FAILURE, "UPS type unknown in upsdrv_init_fc()");
|
||||
}
|
||||
|
||||
if (memcmp(fcstring+3, "01", 2) == 0) {
|
||||
fatalx(EXIT_FAILURE, "Best Patriot UPS not supported");
|
||||
}
|
||||
else if (memcmp(fcstring+3, "02", 2) == 0) {
|
||||
snprintf(fc.name, sizeof(fc.name), "%s", "FortressII");
|
||||
fc.type = FORTRESSII;
|
||||
}
|
||||
else if (memcmp(fcstring+3, "03", 2) == 0) {
|
||||
snprintf(fc.name, sizeof(fc.name), "%s", "Ferrups");
|
||||
fc.type = FERRUPS;
|
||||
}
|
||||
else if (memcmp(fcstring+3, "04", 2) == 0) {
|
||||
snprintf(fc.name, sizeof(fc.name), "%s", "Unity/1");
|
||||
fc.type = UNITY1;
|
||||
}
|
||||
|
||||
/* (fc.type == FORTRESSII || fc.type == FERRUPS || fc.type == UNITY1) */
|
||||
if (memcmp(fcstring+5, "00", 2) == 0) {
|
||||
/* fc.model = LI520; */
|
||||
fc.model = LIxxxx;
|
||||
}
|
||||
else if (memcmp(fcstring+5, "01", 2) == 0) {
|
||||
/* fc.model = LI720; */
|
||||
fc.model = LIxxxx;
|
||||
}
|
||||
else if (memcmp(fcstring+5, "02", 2) == 0) {
|
||||
/* fc.model = LI1020; */
|
||||
fc.model = LIxxxx;
|
||||
}
|
||||
else if (memcmp(fcstring+5, "03", 2) == 0) {
|
||||
/* fc.model = LI1420; */
|
||||
fc.model = LIxxxx;
|
||||
}
|
||||
else if (memcmp(fcstring+71, "LI", 2) == 0) {
|
||||
fc.model = LIxxxx;
|
||||
}
|
||||
|
||||
switch(fc.model) {
|
||||
case LIxxxx:
|
||||
fc.va = bcd2i(&fcstring[11], 5);
|
||||
fc.watts = bcd2i(&fcstring[16], 5);
|
||||
|
||||
/* determine shutdown battery voltage */
|
||||
fc.emptyvolts= ((double)bcd2i(&fcstring[57], 4) / 10.0);
|
||||
|
||||
/* determine fully charged battery voltage */
|
||||
fc.lowvolts= ((double)bcd2i(&fcstring[53], 4) / 10.0);
|
||||
|
||||
/* determine fully charged battery voltage */
|
||||
fc.fullvolts= ((double)bcd2i(&fcstring[49], 4) / 10.0);
|
||||
|
||||
/* determine "ideal" voltage by a guess */
|
||||
fc.idealbvolts = ((fc.fullvolts - fc.emptyvolts) * 0.7) + fc.emptyvolts;
|
||||
break;
|
||||
default:
|
||||
fatalx(EXIT_FAILURE, "Unknown model %s in upsdrv_init_fc()", tmp);
|
||||
}
|
||||
fc.valid = 1;
|
||||
}
|
||||
|
||||
void upsdrv_initups ()
|
||||
{
|
||||
char rstring[256];
|
||||
|
||||
upsfd = ser_open(device_path);
|
||||
ser_set_speed(upsfd, device_path, B1200);
|
||||
setup_serial();
|
||||
ups_sync();
|
||||
|
||||
inverter_status = 0;
|
||||
fc.model = UNKNOWN;
|
||||
if (execute("f\r", rstring, sizeof(rstring)) < 1 ) {
|
||||
fatalx(EXIT_FAILURE, "Failed format request in upsdrc_initups()");
|
||||
}
|
||||
|
||||
execute("fc\r", rstring, sizeof(rstring));
|
||||
if (strlen(rstring) < 80 ) {
|
||||
ser_get_line(upsfd, rstring, sizeof(rstring), '>', "\012", 3, 0);
|
||||
upsdrv_init_nofc();
|
||||
} else {
|
||||
upsdrv_init_fc(rstring);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
ser_close(upsfd, device_path);
|
||||
}
|
||||
444
drivers/bestfortress.c
Normal file
444
drivers/bestfortress.c
Normal file
|
|
@ -0,0 +1,444 @@
|
|||
/*
|
||||
bestfortress.c - model specific routines for (very) old Best Power Fortress
|
||||
|
||||
Copyright (C) 2002 Russell Kroll <rkroll@exploits.org> (skeleton)
|
||||
(C) 2002 Holger Dietze <holger.dietze@advis.de>
|
||||
(C) 2009 Stuart D. Gathman <stuart@bmsi.com>
|
||||
|
||||
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "serial.h"
|
||||
|
||||
#define UPSDELAY 50000 /* 50 ms delay required for reliable operation */
|
||||
#define SER_WAIT_SEC 2 /* allow 2.0 sec for ser_get calls */
|
||||
#define SER_WAIT_USEC 0
|
||||
#define ENDCHAR '\r'
|
||||
#define IGNCHARS " \n"
|
||||
|
||||
#if defined(__sgi) && ! defined(__GNUC__)
|
||||
#define inline __inline
|
||||
#endif
|
||||
|
||||
#define DRIVER_NAME "Best Fortress UPS driver"
|
||||
#define DRIVER_VERSION "0.02"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Holger Dietze <holger.dietze@advis.de>\n"
|
||||
"Stuart D. Gathman <stuart@bmsi.com>\n",
|
||||
DRV_EXPERIMENTAL,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static int instcmd (const char *cmdname, const char *extra);
|
||||
static int upsdrv_setvar (const char *varname, const char *val);
|
||||
|
||||
/* rated VA load if known */
|
||||
static int maxload = 0;
|
||||
|
||||
void upsdrv_initinfo(void)
|
||||
{
|
||||
dstate_setinfo("ups.mfr", "Best Power");
|
||||
dstate_setinfo("ups.model", "Fortress");
|
||||
dstate_setinfo("battery.voltage.nominal", "24");
|
||||
|
||||
/*dstate_setinfo ("alarm.overload", "0");*/ /* Flag */
|
||||
/*dstate_setinfo ("alarm.temp", "0");*/ /* Flag */
|
||||
if (maxload)
|
||||
dstate_setinfo("ups.load", "0");
|
||||
dstate_setinfo("output.voltamps", "0");
|
||||
dstate_setinfo("ups.delay.shutdown", "10"); /* write only */
|
||||
|
||||
/* tunable via front panel: (european voltage level)
|
||||
parameter factory default range
|
||||
INFO_LOWXFER 196 V p7=nnn 160-210
|
||||
INFO_HIGHXFER 254 V p8=nnn 215-274
|
||||
INFO_LOBATTIME 2 min p2=n 1-5
|
||||
|
||||
comm mode p6=0 dumb DONT USE (will lose access to parameter setting!)
|
||||
p6=1 B1200
|
||||
p6=2 B2400
|
||||
P6=3 B4800
|
||||
p6=4 B9600
|
||||
maybe cycle through speeds to autodetect?
|
||||
|
||||
echo off e0
|
||||
echo on e1
|
||||
*/
|
||||
dstate_setinfo("input.transfer.low", "%s", "");
|
||||
dstate_setflags("input.transfer.low", ST_FLAG_STRING | ST_FLAG_RW);
|
||||
dstate_setaux("input.transfer.low", 3);
|
||||
|
||||
dstate_setinfo("input.transfer.high", "%s", "");
|
||||
dstate_setflags("input.transfer.high", ST_FLAG_STRING | ST_FLAG_RW);
|
||||
dstate_setaux("input.transfer.high", 3);
|
||||
|
||||
dstate_setinfo("battery.runtime.low", "%s", "");
|
||||
dstate_setflags("battery.runtime.low", ST_FLAG_STRING | ST_FLAG_RW);
|
||||
dstate_setaux("battery.runtime.low", 3);
|
||||
|
||||
upsh.instcmd = instcmd;
|
||||
upsh.setvar = upsdrv_setvar;
|
||||
|
||||
dstate_addcmd("shutdown.return");
|
||||
dstate_addcmd("load.off");
|
||||
}
|
||||
|
||||
/* convert hex digit to int */
|
||||
static inline int fromhex (char c)
|
||||
{
|
||||
return (c >= '0' && c <= '9') ? c - '0'
|
||||
: (c >= 'A' && c <= 'F') ? c - 'A' + 10
|
||||
: (c >= 'a' && c <= 'f') ? c - 'a' + 10
|
||||
: 0;
|
||||
}
|
||||
|
||||
/* do checksumming on UPS response */
|
||||
static int checksum (char * s)
|
||||
{
|
||||
int i;
|
||||
int sum;
|
||||
for (i = 40, sum = 0; s[0] && s[1] && i > 0; i--, s += 2) {
|
||||
sum += (fromhex (s[0]) << 4) + fromhex (s[1]);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
/* set info to integer value */
|
||||
static inline int setinfo_int (const char *key, const char * s, size_t len)
|
||||
{
|
||||
char buf[10];
|
||||
int val;
|
||||
|
||||
if (len > sizeof(buf)) len = sizeof(buf)-1;
|
||||
strncpy (buf, s, len);
|
||||
buf[len] = 0;
|
||||
val = atoi(buf);
|
||||
dstate_setinfo (key, "%d", val);
|
||||
return val;
|
||||
}
|
||||
|
||||
/* set info to integer value (for runtime remaining)
|
||||
value is expressed in minutes, but desired in seconds
|
||||
*/
|
||||
static inline void setinfo_int_minutes (const char *key, const char * s, size_t len)
|
||||
{
|
||||
char buf[10];
|
||||
|
||||
if (len > sizeof(buf)) len = sizeof(buf)-1;
|
||||
strncpy (buf, s, len);
|
||||
buf[len] = 0;
|
||||
dstate_setinfo (key, "%d", 60*atoi (buf));
|
||||
}
|
||||
|
||||
/* set info to float value */
|
||||
static inline void setinfo_float (const char *key, char * fmt, const char * s, size_t len, double factor)
|
||||
{
|
||||
char buf[10];
|
||||
if (len > sizeof(buf)) len = sizeof(buf)-1;
|
||||
strncpy (buf, s, len);
|
||||
buf[len] = 0;
|
||||
dstate_setinfo (key, fmt, factor * (double)atoi (buf));
|
||||
}
|
||||
|
||||
static int upssend(const char *fmt,...) {
|
||||
int ret;
|
||||
char buf[1024], *p;
|
||||
va_list ap;
|
||||
unsigned int sent = 0;
|
||||
int d_usec = UPSDELAY;
|
||||
|
||||
va_start(ap, fmt);
|
||||
ret = vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
if ((ret < 1) || (ret >= (int) sizeof(buf)))
|
||||
upslogx(LOG_WARNING, "ser_send_pace: vsnprintf needed more "
|
||||
"than %d bytes", (int)sizeof(buf));
|
||||
for (p = buf; *p; p++) {
|
||||
if (write(upsfd, p, 1) != 1)
|
||||
return -1;
|
||||
|
||||
if (d_usec)
|
||||
usleep(d_usec);
|
||||
|
||||
sent++;
|
||||
}
|
||||
|
||||
return sent;
|
||||
}
|
||||
|
||||
static int upsrecv(char *buf,size_t bufsize,char ec,const char *ic)
|
||||
{
|
||||
return ser_get_line(upsfd, buf, bufsize - 1, ec, ic,
|
||||
SER_WAIT_SEC, SER_WAIT_USEC);
|
||||
}
|
||||
|
||||
static int upsflushin(int f,int verbose,const char *ignset)
|
||||
{
|
||||
return ser_flush_in(upsfd, ignset, verbose);
|
||||
}
|
||||
|
||||
/* read out UPS and store info */
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
char temp[256];
|
||||
char *p;
|
||||
int loadva;
|
||||
int len;
|
||||
int retry;
|
||||
|
||||
int checksum_ok, is_online=1, is_off, low_batt, trimming, boosting;
|
||||
|
||||
for (retry = 0; retry < 5; ++retry) {
|
||||
upsflushin (0, 0, "\r ");
|
||||
upssend ("f\r");
|
||||
do {
|
||||
if (upsrecv (temp+2, sizeof temp - 2, ENDCHAR, IGNCHARS) <= 0) {
|
||||
upsflushin (0, 0, "\r ");
|
||||
upssend ("f\r");
|
||||
}
|
||||
} while (temp[2] == 0);
|
||||
|
||||
/*syslog (LOG_DAEMON | LOG_NOTICE,"ups: got '%s'\n", p);*/
|
||||
/* status example:
|
||||
000000000001000000000000012201210000001200014500000280600000990025000000000301BE
|
||||
000000000001000000000000012401230000001200014800000280600000990025000000000301B7
|
||||
|Vi||Vo| |Io||Psou| |Vb||f| |tr||Ti| CS
|
||||
000000000001000000000000023802370000000200004700000267500000990030000000000301BD
|
||||
1 1 2 2 3 3 4 4 5 5 6 6 7 7 78
|
||||
0 5 0 5 0 5 0 5 0 5 0 5 0 5 0 5 90
|
||||
*/
|
||||
|
||||
/* last bytes are a checksum:
|
||||
interpret response as hex string, sum of all bytes must be zero
|
||||
*/
|
||||
checksum_ok = (checksum (temp+2) & 0xff) == 0;
|
||||
/* setinfo (INFO_, ""); */
|
||||
|
||||
/* I can't figure out why this is missing the first two chars.
|
||||
But the first two chars are not used, so just set them to zero
|
||||
when missing. */
|
||||
len = strlen(temp+2);
|
||||
temp[0] = '0';
|
||||
temp[1] = '0';
|
||||
p = temp+2;
|
||||
if (len == 78)
|
||||
p = temp;
|
||||
else if (len != 80)
|
||||
checksum_ok = 0;
|
||||
if (checksum_ok) break;
|
||||
sleep(SER_WAIT_SEC);
|
||||
}
|
||||
|
||||
if (!checksum_ok) {
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
/* upslogx(LOG_INFO, "updateinfo: %s", p); */
|
||||
|
||||
setinfo_int ("input.voltage", p+24,4);
|
||||
setinfo_int ("output.voltage", p+28,4);
|
||||
setinfo_float ("battery.voltage", "%.1f", p+50,4, 0.1);
|
||||
setinfo_float ("output.current", "%.1f", p+36,4, 0.1);
|
||||
loadva = setinfo_int ("output.voltamps", p+40,6);
|
||||
if (maxload)
|
||||
dstate_setinfo ("ups.load", "%d", loadva * 100 / maxload);
|
||||
setinfo_float ("input.frequency", "%.1f", p+54,3, 0.1);
|
||||
setinfo_int_minutes ("battery.runtime", p+58,4);
|
||||
setinfo_int ("ups.temperature", p+62,4);
|
||||
|
||||
is_online = p[17] == '0';
|
||||
low_batt = fromhex(p[21]) & 8 || fromhex(p[20]) & 1;
|
||||
is_off = p[11] == '0';
|
||||
trimming = p[33] == '1';
|
||||
boosting = 0; /* FIXME, don't know which bit gets set
|
||||
(brownouts are very rare here and I can't
|
||||
simulate one) */
|
||||
|
||||
status_init();
|
||||
if (low_batt)
|
||||
status_set("LB ");
|
||||
else if (trimming)
|
||||
status_set("TRIM");
|
||||
else if (boosting)
|
||||
status_set("BOOST");
|
||||
else
|
||||
status_set(is_online ? (is_off ? "OFF " : "OL ") : "OB ");
|
||||
|
||||
/* setinfo(INFO_STATUS, "%s%s",
|
||||
* (util < lownorm) ? "BOOST ", "",
|
||||
* (util > highnorm) ? "TRIM ", "",
|
||||
* ((flags & TIOCM_CD) == 0) ? "" : "LB ",
|
||||
* ((flags & TIOCM_CTS) == TIOCM_CTS) ? "OB" : "OL");
|
||||
*/
|
||||
|
||||
status_commit();
|
||||
dstate_dataok();
|
||||
}
|
||||
|
||||
|
||||
/* Parameter setting */
|
||||
|
||||
/* all UPS tunable parameters are set with command
|
||||
'p%d=%s'
|
||||
*/
|
||||
int setparam (int parameter, int dlen, const char * data)
|
||||
{
|
||||
char reply[80];
|
||||
upssend ("p%d=%*s\r", parameter, dlen, data);
|
||||
if (upsrecv (reply, sizeof(reply), ENDCHAR, "") < 0) return 0;
|
||||
return strncmp (reply, "OK", 2) == 0;
|
||||
}
|
||||
|
||||
/* ups_setsuper: set super-user access
|
||||
(allows setting variables)
|
||||
*/
|
||||
static void ups_setsuper (int super)
|
||||
{
|
||||
setparam (999, super ? 4 : 0, super ? "2639" : "");
|
||||
}
|
||||
|
||||
/* sets whether UPS will reapply power after it has shut down and line
|
||||
* power returns.
|
||||
*/
|
||||
static void autorestart (int restart)
|
||||
{
|
||||
ups_setsuper (1);
|
||||
setparam (1, 1, restart ? "1" : "0");
|
||||
ups_setsuper (0);
|
||||
}
|
||||
|
||||
/* set UPS parameters */
|
||||
static int upsdrv_setvar (const char *var, const char * data) {
|
||||
int parameter;
|
||||
int len = strlen(data);
|
||||
upsdebugx(1, "Setvar: %s %s", var, data);
|
||||
if (strcmp("input.transfer.low", var) == 0) {
|
||||
parameter = 7;
|
||||
}
|
||||
else if (strcmp("input.transfer.high", var) == 0) {
|
||||
parameter = 8;
|
||||
}
|
||||
else if (strcmp("battery.runtime.low", var) == 0) {
|
||||
parameter = 2;
|
||||
}
|
||||
else {
|
||||
upslogx(LOG_INFO, "Setvar: unsettable variable %s", var);
|
||||
return STAT_SET_UNKNOWN;
|
||||
}
|
||||
ups_setsuper (1);
|
||||
if (setparam (parameter, len, data)) {
|
||||
dstate_setinfo (var, "%*s", len, data);
|
||||
}
|
||||
ups_setsuper (0);
|
||||
return STAT_SET_HANDLED;
|
||||
}
|
||||
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
const char *grace;
|
||||
|
||||
grace = dstate_getinfo("ups.delay.shutdown");
|
||||
|
||||
if (!grace)
|
||||
grace = "1"; /* apparently, OFF0 does not work */
|
||||
|
||||
printf ("shutdown in %s seconds\n", grace);
|
||||
/* make power return when utility power returns */
|
||||
autorestart (1);
|
||||
upssend ("OFF%s\r", grace);
|
||||
/* I'm nearly dead, Jim */
|
||||
/* OFF will powercycle when line power is available again */
|
||||
}
|
||||
|
||||
static int instcmd (const char *cmdname, const char *extra)
|
||||
{
|
||||
const char *p;
|
||||
|
||||
if (!strcasecmp(cmdname, "load.off")) {
|
||||
printf ("powering off\n");
|
||||
autorestart (0);
|
||||
upssend ("OFF1\r");
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
else if (!strcasecmp(cmdname, "shutdown.return")) {
|
||||
p = dstate_getinfo ("ups.delay.shutdown");
|
||||
if (!p) p = "1";
|
||||
printf ("shutdown in %s seconds\n", p);
|
||||
autorestart (1);
|
||||
upssend ("OFF%s\r", p);
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
upslogx(LOG_INFO, "instcmd: unknown command %s", cmdname);
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
}
|
||||
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
}
|
||||
|
||||
/* list flags and values that you want to receive via -x */
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
addvar (VAR_VALUE, "baudrate", "serial line speed");
|
||||
addvar (VAR_VALUE, "max_load", "rated VA load VA");
|
||||
}
|
||||
|
||||
struct {
|
||||
char * val;
|
||||
speed_t speed;
|
||||
} speed_table[] = {
|
||||
{"1200", B1200},
|
||||
{"2400", B2400},
|
||||
{"4800", B4800},
|
||||
{"9600", B9600},
|
||||
{NULL, B1200},
|
||||
};
|
||||
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
speed_t speed = B1200;
|
||||
|
||||
char * speed_val = getval("baudrate");
|
||||
char * max_load = getval("max_load");
|
||||
|
||||
if (max_load) maxload = atoi(max_load);
|
||||
|
||||
if (speed_val) {
|
||||
int i;
|
||||
for (i=0; speed_table[i].val; i++) {
|
||||
if (strcmp (speed_val, speed_table[i].val) == 0)
|
||||
break;
|
||||
}
|
||||
speed = speed_table[i].speed;
|
||||
}
|
||||
|
||||
upsfd = ser_open(device_path);
|
||||
ser_set_speed(upsfd, device_path, speed);
|
||||
/* TODO: probe ups type */
|
||||
|
||||
/* the upsh handlers can't be done here, as they get initialized
|
||||
* shortly after upsdrv_initups returns to main.
|
||||
*/
|
||||
}
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
}
|
||||
497
drivers/bestuferrups.c
Normal file
497
drivers/bestuferrups.c
Normal file
|
|
@ -0,0 +1,497 @@
|
|||
/*
|
||||
bestuferrups.c - model specific routines for Best Power Micro-Ferrups
|
||||
|
||||
This module is a 40% rewritten mangle of the bestfort module by
|
||||
Grant, which is a 75% rewritten mangle of the bestups module by
|
||||
Russell. It has no test battery command since my ME3100 does this
|
||||
by itself. (same as Grant's driver in this respect)
|
||||
|
||||
Support for model RE added by Tim Thompson (7/22/04)
|
||||
|
||||
Copyright (C) 2002 Andreas Wrede <andreas@planix.com>
|
||||
Copyright (C) 2000 John Stone <johns@megapixel.com>
|
||||
Copyright (C) 2000 Grant Taylor <gtaylor@picante.com>
|
||||
Copyright (C) 1999 Russell Kroll <rkroll@exploits.org>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "serial.h"
|
||||
|
||||
#define DRIVER_NAME "Best Ferrups Series ME/RE/MD driver"
|
||||
#define DRIVER_VERSION "0.03"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Andreas Wrede <andreas@planix.com>\n" \
|
||||
"John Stone <johns@megapixel.com>\n" \
|
||||
"Grant Taylor <gtaylor@picante.com>\n" \
|
||||
"Russell Kroll <rkroll@exploits.org>\n" \
|
||||
"Tim Thompson",
|
||||
DRV_BETA, /* FIXME: STABLE? */
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
#define ENDCHAR '\r'
|
||||
#define IGNCHARS "\012"
|
||||
|
||||
/* UPS Model Codes */
|
||||
#define UNKNOWN 100
|
||||
#define ME3100 200
|
||||
#define MD1KVA 300 /* Software version P5.05 dated 05/18/89 */
|
||||
#define RE1800 400
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int debugging = 0;
|
||||
|
||||
|
||||
/* Blob of UPS configuration data from the formatconfig string */
|
||||
struct {
|
||||
int valid; /* set to 1 when this is filled in */
|
||||
|
||||
float idealbvolts; /* various interestin battery voltages */
|
||||
float fullvolts;
|
||||
float emptyvolts;
|
||||
int va; /* capacity of UPS in Volt-Amps */
|
||||
int watts; /* capacity of UPS in watts */
|
||||
int model; /* enumerated model type */
|
||||
} fc;
|
||||
|
||||
|
||||
/* Forward decls */
|
||||
|
||||
/* Set up all the funky shared memory stuff used to communicate with upsd */
|
||||
void upsdrv_initinfo (void)
|
||||
{
|
||||
/* now set up room for all future variables that are supported */
|
||||
|
||||
dstate_setinfo("ups.mfr", "%s", "Best Power");
|
||||
switch(fc.model) {
|
||||
case ME3100:
|
||||
dstate_setinfo("ups.model", "Micro Ferrups (ME) %d", fc.va);
|
||||
break;
|
||||
case MD1KVA:
|
||||
dstate_setinfo("ups.model", "Micro Ferrups (MD) %d", fc.va);
|
||||
break;
|
||||
case RE1800:
|
||||
dstate_setinfo("ups.model", "Micro Ferrups (RE) %d", fc.va);
|
||||
break;
|
||||
default:
|
||||
fatalx(EXIT_FAILURE, "UPS model not matched!"); /* Will never get here, upsdrv_initups() will catch */
|
||||
}
|
||||
fprintf(stderr, "Best Power %s detected\n",
|
||||
dstate_getinfo("ups.model"));
|
||||
fprintf(stderr, "Battery voltages %5.1f nominal, %5.1f full, %5.1f empty\n",
|
||||
fc.idealbvolts,
|
||||
fc.fullvolts,
|
||||
fc.emptyvolts);
|
||||
}
|
||||
|
||||
|
||||
/* Debugging display from kermit:
|
||||
|
||||
----------------------------------------------------
|
||||
time^M^M^JFeb 20, 22:13:32^M^J^M^J=>id^M^JUnit ID "ME3.1K12345"^M^J^M^J=>
|
||||
----------------------------------------------------
|
||||
*/
|
||||
|
||||
static int execute(const char *cmd, char *result, int resultsize)
|
||||
{
|
||||
int ret;
|
||||
char buf[256];
|
||||
|
||||
ser_send(upsfd, "%s", cmd);
|
||||
ser_get_line(upsfd, buf, sizeof(buf), '\012', "", 3, 0);
|
||||
ret = ser_get_line(upsfd, result, resultsize, '\015', "\012", 3, 0);
|
||||
ser_get_line(upsfd, buf, sizeof(buf), '>', "", 3, 0);
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
char fstring[512];
|
||||
|
||||
if (! fc.valid) {
|
||||
fprintf(stderr,
|
||||
"upsupdate run before ups_ident() read ups config\n");
|
||||
assert(0);
|
||||
}
|
||||
|
||||
if (execute("f\r", fstring, sizeof(fstring)) > 0) {
|
||||
int inverter=0, charger=0, vin=0, vout=0, btimeleft=0, linestat=0,
|
||||
alstat=0, vaout=0;
|
||||
double ampsout=0.0, vbatt=0.0, battpercent=0.0, loadpercent=0.0,
|
||||
hstemp=0.0, acfreq=0.0, ambtemp=0.0;
|
||||
char tmp[16];
|
||||
|
||||
/* Inverter status. 0=off 1=on */
|
||||
memcpy(tmp, fstring+16, 2);
|
||||
tmp[2] = '\0';
|
||||
inverter = atoi(tmp);
|
||||
|
||||
/* Charger status. 0=off 1=on */
|
||||
memcpy(tmp, fstring+18, 2);
|
||||
tmp[2] = '\0';
|
||||
charger = atoi(tmp);
|
||||
|
||||
/* Input Voltage. integer number */
|
||||
memcpy(tmp, fstring+24, 4);
|
||||
tmp[4] = '\0';
|
||||
vin = atoi(tmp);
|
||||
|
||||
/* Output Voltage. integer number */
|
||||
memcpy(tmp, fstring+28, 4);
|
||||
tmp[4] = '\0';
|
||||
vout = atoi(tmp);
|
||||
|
||||
/* Iout. int times 10 */
|
||||
memcpy(tmp, fstring+36, 4);
|
||||
tmp[4] = '\0';
|
||||
ampsout = ((double)(atoi(tmp)) / 10.0);
|
||||
|
||||
/* Battery voltage. int times 10 */
|
||||
memcpy(tmp, fstring+50, 4);
|
||||
tmp[4] = '\0';
|
||||
vbatt = ((double)(atoi(tmp)) / 10.0);
|
||||
|
||||
/* Volt-amps out. int */
|
||||
memcpy(tmp, fstring+40, 6);
|
||||
tmp[6] = '\0';
|
||||
vaout = atoi(tmp);
|
||||
|
||||
/* Line status. Bitmask */
|
||||
memcpy(tmp, fstring+72, 2);
|
||||
tmp[2] = '\0';
|
||||
linestat = atoi(tmp);
|
||||
|
||||
/* Alarm status reg 1. Bitmask */
|
||||
memcpy(tmp, fstring+20, 2);
|
||||
tmp[2] = '\0';
|
||||
alstat = atoi(tmp);
|
||||
|
||||
/* Alarm status reg 2. Bitmask */
|
||||
memcpy(tmp, fstring+22, 2);
|
||||
tmp[2] = '\0';
|
||||
alstat = alstat | (atoi(tmp) << 8);
|
||||
|
||||
/* AC line frequency */
|
||||
memcpy(tmp, fstring+54, 4);
|
||||
tmp[4]= '\0';
|
||||
acfreq = ((double)(atoi(tmp)) / 100.0);
|
||||
|
||||
/* Runtime remaining */
|
||||
memcpy(tmp, fstring+58, 4);
|
||||
tmp[4]= '\0';
|
||||
btimeleft = atoi(tmp);
|
||||
|
||||
/* UPS Temperature */
|
||||
memcpy(tmp, fstring+62, 4);
|
||||
tmp[4]= '\0';
|
||||
ambtemp = (double)(atoi(tmp));
|
||||
|
||||
/* Percent Load */
|
||||
switch(fc.model) {
|
||||
case ME3100:
|
||||
if (execute("d 16\r", fstring, sizeof(fstring)) > 0) {
|
||||
int l;
|
||||
sscanf(fstring, "16 FullLoad%% %d", &l);
|
||||
loadpercent = (double) l;
|
||||
}
|
||||
break;
|
||||
case RE1800:
|
||||
if (execute("d 16\r", fstring, sizeof(fstring)) > 0) {
|
||||
int l;
|
||||
sscanf(fstring, "16 FullLoad%% %d", &l);
|
||||
loadpercent = (double) l;
|
||||
}
|
||||
if (execute("d 12\r", fstring, sizeof(fstring)) > 0) {
|
||||
int l;
|
||||
sscanf(fstring, "12 HS Temp %dC", &l);
|
||||
hstemp = (double) l;
|
||||
}
|
||||
break;
|
||||
case MD1KVA:
|
||||
if (execute("d 22\r", fstring, sizeof(fstring)) > 0) {
|
||||
int l;
|
||||
sscanf(fstring, "22 FullLoad%% %d", &l);
|
||||
loadpercent = (double) l;
|
||||
}
|
||||
break;
|
||||
default: /* Will never happen, caught in upsdrv_initups() */
|
||||
fatalx(EXIT_FAILURE, "Unknown model in upsdrv_updateinfo()");
|
||||
}
|
||||
/* Compute battery percent left based on battery voltages. */
|
||||
battpercent = ((vbatt - fc.emptyvolts)
|
||||
/ (fc.fullvolts - fc.emptyvolts) * 100.0);
|
||||
if (battpercent < 0.0)
|
||||
battpercent = 0.0;
|
||||
else if (battpercent > 100.0)
|
||||
battpercent = 100.0;
|
||||
|
||||
/* Compute status string */
|
||||
{
|
||||
int lowbatt, overload, replacebatt, boosting, trimming;
|
||||
|
||||
lowbatt = alstat & (1<<1);
|
||||
overload = alstat & (1<<6);
|
||||
replacebatt = alstat & (1<<10);
|
||||
boosting = inverter && (linestat & (1<<2)) && (vin < 115);
|
||||
trimming = inverter && (linestat & (1<<2)) && (vin > 115);
|
||||
|
||||
status_init();
|
||||
|
||||
if (inverter)
|
||||
status_set("OB");
|
||||
else
|
||||
status_set("OL");
|
||||
|
||||
if (lowbatt)
|
||||
status_set("LB");
|
||||
|
||||
if (trimming)
|
||||
status_set("TRIM");
|
||||
|
||||
if (boosting)
|
||||
status_set("BOOST");
|
||||
|
||||
if (replacebatt)
|
||||
status_set("RB");
|
||||
|
||||
if (overload)
|
||||
status_set("OVER");
|
||||
|
||||
status_commit();
|
||||
}
|
||||
|
||||
if (debugging) {
|
||||
fprintf(stderr,
|
||||
"Poll: inverter %d charger %d vin %d vout %d vaout %d btimeleft %d\n",
|
||||
inverter, charger, vin, vout, vaout, btimeleft);
|
||||
fprintf(stderr,
|
||||
" ampsout %5.1f vbatt %5.1f batpcnt %5.1f loadpcnt %5.1f upstemp %5.1f acfreq %5.2f ambtemp %5.1f\n",
|
||||
ampsout, vbatt, battpercent, loadpercent, hstemp, acfreq, ambtemp);
|
||||
|
||||
}
|
||||
|
||||
/* Stuff information into info structures */
|
||||
|
||||
dstate_setinfo("input.voltage", "%05.1f", (double)vin);
|
||||
dstate_setinfo("output.voltage", "%05.1f", (double)vout);
|
||||
dstate_setinfo("battery.charge", "%02.1f", battpercent);
|
||||
dstate_setinfo("ups.load", "%02.1f", loadpercent);
|
||||
dstate_setinfo("battery.voltage", "%02.1f", vbatt);
|
||||
dstate_setinfo("input.frequency", "%05.2f", (double)acfreq);
|
||||
dstate_setinfo("ups.temperature", "%05.1f", (double)hstemp);
|
||||
dstate_setinfo("battery.runtime", "%d", btimeleft);
|
||||
dstate_setinfo("ambient.temperature", "%05.1f", (double)ambtemp);
|
||||
|
||||
dstate_dataok();
|
||||
/* Tim: With out this return, it always falls over to the
|
||||
datastate() at the end of the function */
|
||||
return;
|
||||
} else {
|
||||
|
||||
dstate_datastale();
|
||||
|
||||
} /* if (execute("f\r", fstring, sizeof(fstring)) > 0) */
|
||||
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
static void ups_sync(void)
|
||||
{
|
||||
char buf[256];
|
||||
|
||||
printf ("Syncing: ");
|
||||
fflush (stdout);
|
||||
|
||||
/* A bit better sanity might be good here. As is, we expect the
|
||||
human to observe the time being totally not a time. */
|
||||
|
||||
if (execute("time\r", buf, sizeof(buf)) > 0) {
|
||||
fprintf(stderr, "UPS Time: %s\n", buf);
|
||||
} else {
|
||||
fatalx(EXIT_FAILURE, "Error connecting to UPS");
|
||||
}
|
||||
}
|
||||
|
||||
/* power down the attached load immediately */
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
/* NB: hard-wired password */
|
||||
ser_send(upsfd, "pw377\r");
|
||||
ser_send(upsfd, "off 1 a\r"); /* power off in 1 second and restart when line power returns */
|
||||
}
|
||||
|
||||
/* list flags and values that you want to receive via -x */
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
}
|
||||
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
}
|
||||
|
||||
static void sync_serial(void) {
|
||||
char buffer[10];
|
||||
|
||||
ser_send(upsfd, "\r");
|
||||
ser_get_line(upsfd, buffer, sizeof(buffer), '\r', "\012", 3, 0);
|
||||
ser_get_line(upsfd, buffer, sizeof(buffer), ENDCHAR, IGNCHARS, 3, 0);
|
||||
|
||||
while (ser_get_line(upsfd, buffer, sizeof(buffer), '>', "\012", 3, 0) <= 0) {
|
||||
ser_send(upsfd, "\r");
|
||||
}
|
||||
}
|
||||
|
||||
/* Begin code stolen from bestups.c */
|
||||
static void setup_serial(void)
|
||||
{
|
||||
struct termios tio;
|
||||
|
||||
if (tcgetattr(upsfd, &tio) == -1)
|
||||
fatal_with_errno(EXIT_FAILURE, "tcgetattr");
|
||||
|
||||
tio.c_iflag = IXON | IXOFF;
|
||||
tio.c_oflag = 0;
|
||||
tio.c_cflag = (CS8 | CREAD | HUPCL | CLOCAL);
|
||||
tio.c_lflag = 0;
|
||||
tio.c_cc[VMIN] = 1;
|
||||
tio.c_cc[VTIME] = 0;
|
||||
|
||||
#ifdef HAVE_CFSETISPEED
|
||||
cfsetispeed(&tio, B1200); /* baud change here */
|
||||
cfsetospeed(&tio, B1200);
|
||||
#else
|
||||
#error This system lacks cfsetispeed() and has no other means to set the speed
|
||||
#endif
|
||||
|
||||
if (tcsetattr(upsfd, TCSANOW, &tio) == -1)
|
||||
fatal_with_errno(EXIT_FAILURE, "tcsetattr");
|
||||
/* end code stolen from bestups.c */
|
||||
|
||||
sync_serial();
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_initups ()
|
||||
{
|
||||
char temp[256], fcstring[512];
|
||||
|
||||
upsfd = ser_open(device_path);
|
||||
ser_set_speed(upsfd, device_path, B1200);
|
||||
setup_serial();
|
||||
ups_sync();
|
||||
|
||||
fc.model = UNKNOWN;
|
||||
/* Obtain Model */
|
||||
if (execute("id\r", fcstring, sizeof(fcstring)) < 1) {
|
||||
fatalx(EXIT_FAILURE, "Failed execute in ups_ident()");
|
||||
}
|
||||
|
||||
/* response is a one-line packed string starting with $ */
|
||||
if (memcmp(fcstring, "Unit", 4)) {
|
||||
fatalx(EXIT_FAILURE,
|
||||
"Bad response from formatconfig command in ups_ident()\n"
|
||||
"id: %s\n", fcstring
|
||||
);
|
||||
}
|
||||
|
||||
if (debugging)
|
||||
fprintf(stderr, "id: %s\n", fcstring);
|
||||
|
||||
/* chars 4:2 are a two-digit ascii hex enumerated model code */
|
||||
memcpy(temp, fcstring+9, 2);
|
||||
temp[2] = '\0';
|
||||
|
||||
if (memcmp(temp, "ME", 2) == 0) {
|
||||
fc.model = ME3100;
|
||||
} else if ((memcmp(temp, "RE", 2) == 0)) {
|
||||
fc.model = RE1800;
|
||||
} else if (memcmp(temp, "C1", 2) == 0) {
|
||||
/* Better way to identify unit is using "d 15\r", which results in
|
||||
"15 M# MD1KVA", "id\r" yields "Unit ID "C1K03588"" */
|
||||
fc.model = MD1KVA;
|
||||
}
|
||||
|
||||
switch(fc.model) {
|
||||
case ME3100:
|
||||
fc.va = 3100;
|
||||
fc.watts = 2200;
|
||||
/* determine shutdown battery voltage */
|
||||
if (execute("d 29\r", fcstring, sizeof(fcstring)) > 0) {
|
||||
sscanf(fcstring, "29 LowBat %f", &fc.emptyvolts);
|
||||
}
|
||||
/* determine fully charged battery voltage */
|
||||
if (execute("d 31\r", fcstring, sizeof(fcstring)) > 0) {
|
||||
sscanf(fcstring, "31 HiBatt %f", &fc.fullvolts);
|
||||
}
|
||||
fc.fullvolts = 54.20;
|
||||
/* determine "ideal" voltage by a guess */
|
||||
fc.idealbvolts = ((fc.fullvolts - fc.emptyvolts) * 0.7) + fc.emptyvolts;
|
||||
break;
|
||||
case RE1800:
|
||||
fc.va = 1800;
|
||||
fc.watts = 1200;
|
||||
/* determine shutdown battery voltage */
|
||||
if (execute("d 29\r", fcstring, sizeof(fcstring)) > 0) {
|
||||
sscanf(fcstring, "29 LowBat %f", &fc.emptyvolts);
|
||||
}
|
||||
/* determine fully charged battery voltage */
|
||||
if (execute("d 31\r", fcstring, sizeof(fcstring)) > 0) {
|
||||
sscanf(fcstring, "31 HiBatt %f", &fc.fullvolts);
|
||||
}
|
||||
fc.fullvolts = 54.20;
|
||||
/* determine "ideal" voltage by a guess */
|
||||
fc.idealbvolts = ((fc.fullvolts - fc.emptyvolts) * 0.7) + fc.emptyvolts;
|
||||
break;
|
||||
case MD1KVA:
|
||||
fc.va = 1100;
|
||||
fc.watts = 770; /* Approximate, based on 0.7 power factor */
|
||||
/* determine shutdown battery voltage */
|
||||
if (execute("d 27\r", fcstring, sizeof(fcstring)) > 0) {
|
||||
sscanf(fcstring, "27 LowBatt %f", &fc.emptyvolts);
|
||||
}
|
||||
/* determine fully charged battery voltage */
|
||||
if (execute("d 28\r", fcstring, sizeof(fcstring)) > 0) {
|
||||
sscanf(fcstring, "28 Hi Batt %f", &fc.fullvolts);
|
||||
}
|
||||
fc.fullvolts = 13.70;
|
||||
/* determine "ideal" voltage by a guess */
|
||||
fc.idealbvolts = ((fc.fullvolts - fc.emptyvolts) * 0.7) + fc.emptyvolts;
|
||||
break;
|
||||
default:
|
||||
fatalx(EXIT_FAILURE, "Uknown model %s in ups_ident()", temp);
|
||||
}
|
||||
|
||||
fc.valid = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
ser_close(upsfd, device_path);
|
||||
}
|
||||
437
drivers/bestups.c
Normal file
437
drivers/bestups.c
Normal file
|
|
@ -0,0 +1,437 @@
|
|||
/* bestups.c - model specific routines for Best-UPS Fortress models
|
||||
|
||||
Copyright (C) 1999 Russell Kroll <rkroll@exploits.org>
|
||||
|
||||
ID config option by Jason White <jdwhite@jdwhite.org>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "serial.h"
|
||||
|
||||
#define DRIVER_NAME "Best UPS driver"
|
||||
#define DRIVER_VERSION "1.05"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Russell Kroll <rkroll@exploits.org>\n" \
|
||||
"Jason White <jdwhite@jdwhite.org>",
|
||||
DRV_STABLE,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
#define ENDCHAR 13 /* replies end with CR */
|
||||
#define MAXTRIES 5
|
||||
#define UPSDELAY 50000 /* 50 ms delay required for reliable operation */
|
||||
|
||||
#define SER_WAIT_SEC 3 /* allow 3.0 sec for ser_get calls */
|
||||
#define SER_WAIT_USEC 0
|
||||
|
||||
static float lowvolt = 0, highvolt = 0;
|
||||
static int battvoltmult = 1;
|
||||
static int inverted_bypass_bit = 0;
|
||||
|
||||
static void model_set(const char *abbr, const char *rating)
|
||||
{
|
||||
if (!strcmp(abbr, "FOR")) {
|
||||
dstate_setinfo("ups.mfr", "%s", "Best Power");
|
||||
dstate_setinfo("ups.model", "Fortress %s", rating);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!strcmp(abbr, "FTC")) {
|
||||
dstate_setinfo("ups.mfr", "%s", "Best Power");
|
||||
dstate_setinfo("ups.model", "Fortress Telecom %s", rating);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!strcmp(abbr, "PRO")) {
|
||||
dstate_setinfo("ups.mfr", "%s", "Best Power");
|
||||
dstate_setinfo("ups.model", "Patriot Pro %s", rating);
|
||||
inverted_bypass_bit = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!strcmp(abbr, "PR2")) {
|
||||
dstate_setinfo("ups.mfr", "%s", "Best Power");
|
||||
dstate_setinfo("ups.model", "Patriot Pro II %s", rating);
|
||||
inverted_bypass_bit = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!strcmp(abbr, "325")) {
|
||||
dstate_setinfo("ups.mfr", "%s", "Sola Australia");
|
||||
dstate_setinfo("ups.model", "Sola 325 %s", rating);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!strcmp(abbr, "520")) {
|
||||
dstate_setinfo("ups.mfr", "%s", "Sola Australia");
|
||||
dstate_setinfo("ups.model", "Sola 520 %s", rating);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!strcmp(abbr, "610")) {
|
||||
dstate_setinfo("ups.mfr", "%s", "Best Power");
|
||||
dstate_setinfo("ups.model", "610 %s", rating);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!strcmp(abbr, "620")) {
|
||||
dstate_setinfo("ups.mfr", "%s", "Sola Australia");
|
||||
dstate_setinfo("ups.model", "Sola 620 %s", rating);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!strcmp(abbr, "AX1")) {
|
||||
dstate_setinfo("ups.mfr", "%s", "Best Power");
|
||||
dstate_setinfo("ups.model", "Axxium Rackmount %s", rating);
|
||||
return;
|
||||
}
|
||||
|
||||
dstate_setinfo("ups.mfr", "%s", "Unknown");
|
||||
dstate_setinfo("ups.model", "Unknown %s (%s)", abbr, rating);
|
||||
|
||||
printf("Unknown model detected - please report this ID: '%s'\n", abbr);
|
||||
}
|
||||
|
||||
static int instcmd(const char *cmdname, const char *extra)
|
||||
{
|
||||
if (!strcasecmp(cmdname, "test.battery.stop")) {
|
||||
ser_send_pace(upsfd, UPSDELAY, "CT\r");
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
if (!strcasecmp(cmdname, "test.battery.start")) {
|
||||
ser_send_pace(upsfd, UPSDELAY, "T\r");
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
}
|
||||
|
||||
static int get_ident(char *buf, size_t bufsize)
|
||||
{
|
||||
int i, ret;
|
||||
char *ID;
|
||||
|
||||
ID = getval("ID"); /* user-supplied override from ups.conf */
|
||||
|
||||
if (ID) {
|
||||
upsdebugx(2, "NOTE: using user-supplied ID response");
|
||||
snprintf(buf, bufsize, "%s", ID);
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (i = 0; i < MAXTRIES; i++) {
|
||||
ser_send_pace(upsfd, UPSDELAY, "\rID\r");
|
||||
|
||||
ret = ser_get_line(upsfd, buf, bufsize, ENDCHAR, "",
|
||||
SER_WAIT_SEC, SER_WAIT_USEC);
|
||||
|
||||
if (ret > 0)
|
||||
upsdebugx(2, "get_ident: got [%s]", buf);
|
||||
|
||||
/* buf must start with ( and be in the range [25-27] */
|
||||
if ((ret > 0) && (buf[0] != '(') && (strlen(buf) >= 25) &&
|
||||
(strlen(buf) <= 27))
|
||||
return 1;
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
upslogx(LOG_INFO, "Giving up on hardware detection after %d tries",
|
||||
MAXTRIES);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ups_ident(void)
|
||||
{
|
||||
int i;
|
||||
char buf[256], *ptr;
|
||||
char *model = NULL, *rating = NULL;
|
||||
|
||||
if (!get_ident(buf, sizeof(buf))) {
|
||||
fatalx(EXIT_FAILURE, "Unable to detect a Best/SOLA or Phoenix protocol UPS");
|
||||
}
|
||||
|
||||
/* FOR,750,120,120,20.0,27.6 */
|
||||
ptr = strtok(buf, ",");
|
||||
|
||||
for (i = 0; ptr; i++) {
|
||||
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
model = ptr;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
rating = ptr;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
dstate_setinfo("input.voltage.nominal", "%d", atoi(ptr));
|
||||
break;
|
||||
|
||||
case 3:
|
||||
dstate_setinfo("output.voltage.nominal", "%d", atoi(ptr));
|
||||
break;
|
||||
|
||||
case 4:
|
||||
lowvolt = atof(ptr);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
highvolt = atof(ptr);
|
||||
break;
|
||||
}
|
||||
|
||||
ptr = strtok(NULL, ",");
|
||||
}
|
||||
|
||||
if ((!model) || (!rating)) {
|
||||
fatalx(EXIT_FAILURE, "Didn't get a valid ident string");
|
||||
}
|
||||
|
||||
model_set(model, rating);
|
||||
|
||||
/* Battery voltage multiplier */
|
||||
ptr = getval("battvoltmult");
|
||||
|
||||
if (ptr) {
|
||||
battvoltmult = atoi(ptr);
|
||||
}
|
||||
|
||||
/* Lookup the nominal battery voltage (should be between lowvolt and highvolt */
|
||||
for (i = 0; i < 8; i++) {
|
||||
const int nominal[] = { 2, 6, 12, 24, 36, 48, 72, 96 };
|
||||
|
||||
if ((lowvolt < nominal[i]) && (highvolt > nominal[i])) {
|
||||
dstate_setinfo("battery.voltage.nominal", "%d", battvoltmult * nominal[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ptr = getval("nombattvolt");
|
||||
|
||||
if (ptr) {
|
||||
highvolt = atof(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void ups_sync(void)
|
||||
{
|
||||
char buf[256];
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < MAXTRIES; i++) {
|
||||
ser_send_pace(upsfd, UPSDELAY, "\rQ1\r");
|
||||
|
||||
ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, "",
|
||||
SER_WAIT_SEC, SER_WAIT_USEC);
|
||||
|
||||
/* return once we get something that looks usable */
|
||||
if ((ret > 0) && (buf[0] == '('))
|
||||
return;
|
||||
|
||||
usleep(250000);
|
||||
}
|
||||
|
||||
fatalx(EXIT_FAILURE, "Unable to detect a Best/SOLA or Phoenix protocol UPS");
|
||||
}
|
||||
|
||||
void upsdrv_initinfo(void)
|
||||
{
|
||||
ups_sync();
|
||||
ups_ident();
|
||||
|
||||
printf("Detected %s %s on %s\n", dstate_getinfo("ups.mfr"),
|
||||
dstate_getinfo("ups.model"), device_path);
|
||||
|
||||
/* paranoia - cancel any shutdown that might already be running */
|
||||
ser_send_pace(upsfd, UPSDELAY, "C\r");
|
||||
|
||||
upsh.instcmd = instcmd;
|
||||
|
||||
dstate_addcmd("test.battery.start");
|
||||
dstate_addcmd("test.battery.stop");
|
||||
}
|
||||
|
||||
static int ups_on_line(void)
|
||||
{
|
||||
int i, ret;
|
||||
char temp[256], pstat[32];
|
||||
|
||||
for (i = 0; i < MAXTRIES; i++) {
|
||||
ser_send_pace(upsfd, UPSDELAY, "\rQ1\r");
|
||||
|
||||
ret = ser_get_line(upsfd, temp, sizeof(temp), ENDCHAR, "",
|
||||
SER_WAIT_SEC, SER_WAIT_USEC);
|
||||
|
||||
/* Q1 must return 46 bytes starting with a ( */
|
||||
if ((ret > 0) && (temp[0] == '(') && (strlen(temp) == 46)) {
|
||||
|
||||
sscanf(temp, "%*s %*s %*s %*s %*s %*s %*s %s", pstat);
|
||||
|
||||
if (pstat[0] == '0')
|
||||
return 1; /* on line */
|
||||
|
||||
return 0; /* on battery */
|
||||
}
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
upslogx(LOG_ERR, "Status read failed: assuming on battery");
|
||||
|
||||
return 0; /* on battery */
|
||||
}
|
||||
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
printf("The UPS will shut down in approximately one minute.\n");
|
||||
|
||||
if (ups_on_line())
|
||||
printf("The UPS will restart in about one minute.\n");
|
||||
else
|
||||
printf("The UPS will restart when power returns.\n");
|
||||
|
||||
ser_send_pace(upsfd, UPSDELAY, "S01R0001\r");
|
||||
}
|
||||
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
char involt[16], outvolt[16], loadpct[16], acfreq[16],
|
||||
battvolt[16], upstemp[16], pstat[16], buf[256];
|
||||
float bvoltp;
|
||||
int ret;
|
||||
|
||||
ret = ser_send_pace(upsfd, UPSDELAY, "\rQ1\r");
|
||||
|
||||
if (ret < 1) {
|
||||
ser_comm_fail("ser_send_pace failed");
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
|
||||
/* these things need a long time to respond completely */
|
||||
usleep(200000);
|
||||
|
||||
ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, "",
|
||||
SER_WAIT_SEC, SER_WAIT_USEC);
|
||||
|
||||
if (ret < 1) {
|
||||
ser_comm_fail("Poll failed: %s", ret ? strerror(errno) : "timeout");
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ret < 46) {
|
||||
ser_comm_fail("Poll failed: short read (got %d bytes)", ret);
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ret > 46) {
|
||||
ser_comm_fail("Poll failed: response too long (got %d bytes)",
|
||||
ret);
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
|
||||
if (buf[0] != '(') {
|
||||
ser_comm_fail("Poll failed: invalid start character (got %02x)",
|
||||
buf[0]);
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
|
||||
ser_comm_good();
|
||||
|
||||
sscanf(buf, "%*c%s %*s %s %s %s %s %s %s", involt, outvolt,
|
||||
loadpct, acfreq, battvolt, upstemp, pstat);
|
||||
|
||||
/* Guesstimation of battery charge left (inaccurate) */
|
||||
bvoltp = 100 * (atof(battvolt) - lowvolt) / (highvolt - lowvolt);
|
||||
|
||||
if (bvoltp > 100) {
|
||||
bvoltp = 100;
|
||||
}
|
||||
|
||||
dstate_setinfo("battery.voltage", "%.1f", battvoltmult * atof(battvolt));
|
||||
dstate_setinfo("input.voltage", "%s", involt);
|
||||
dstate_setinfo("output.voltage", "%s", outvolt);
|
||||
dstate_setinfo("ups.load", "%s", loadpct);
|
||||
dstate_setinfo("input.frequency", "%s", acfreq);
|
||||
|
||||
if(upstemp[0] != 'X') {
|
||||
dstate_setinfo("ups.temperature", "%s", upstemp);
|
||||
}
|
||||
|
||||
dstate_setinfo("battery.charge", "%02.1f", bvoltp);
|
||||
|
||||
status_init();
|
||||
|
||||
if (pstat[0] == '0') {
|
||||
status_set("OL"); /* on line */
|
||||
|
||||
/* only allow these when OL since they're bogus when OB */
|
||||
|
||||
if (pstat[2] == (inverted_bypass_bit ? '0' : '1')) {
|
||||
/* boost or trim in effect */
|
||||
if (atof(involt) < atof(outvolt))
|
||||
status_set("BOOST");
|
||||
|
||||
if (atof(involt) > atof(outvolt))
|
||||
status_set("TRIM");
|
||||
}
|
||||
|
||||
} else {
|
||||
status_set("OB"); /* on battery */
|
||||
}
|
||||
|
||||
if (pstat[1] == '1')
|
||||
status_set("LB"); /* low battery */
|
||||
|
||||
status_commit();
|
||||
dstate_dataok();
|
||||
}
|
||||
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
}
|
||||
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
addvar(VAR_VALUE, "nombattvolt", "Override nominal battery voltage");
|
||||
addvar(VAR_VALUE, "ID", "Force UPS ID response string");
|
||||
}
|
||||
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
upsfd = ser_open(device_path);
|
||||
ser_set_speed(upsfd, device_path, B2400);
|
||||
}
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
ser_close(upsfd, device_path);
|
||||
}
|
||||
749
drivers/blazer.c
Normal file
749
drivers/blazer.c
Normal file
|
|
@ -0,0 +1,749 @@
|
|||
/*
|
||||
* blazer.c: driver core for Megatec/Q1 protocol based UPSes
|
||||
*
|
||||
* A document describing the protocol implemented by this driver can be
|
||||
* found online at "http://www.networkupstools.org/protocols/megatec.html".
|
||||
*
|
||||
* Copyright (C) 2008,2009 - Arjen de Korte <adkorte-guest@alioth.debian.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "blazer.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
static int ondelay = 3; /* minutes */
|
||||
static int offdelay = 30; /* seconds */
|
||||
|
||||
static int proto;
|
||||
static int online = 1;
|
||||
|
||||
static struct {
|
||||
double packs; /* battery voltage multiplier */
|
||||
struct {
|
||||
double nom; /* nominal runtime on battery (full load) */
|
||||
double est; /* estimated runtime remaining (full load) */
|
||||
double exp; /* load exponent */
|
||||
} runt;
|
||||
struct {
|
||||
double act; /* actual battery voltage */
|
||||
double high; /* battery float voltage */
|
||||
double nom; /* nominal battery voltage */
|
||||
double low; /* battery low voltage */
|
||||
} volt;
|
||||
struct {
|
||||
double act; /* actual battery charge */
|
||||
long time; /* recharge time from empty to full */
|
||||
} chrg;
|
||||
} batt = { 1, { -1, 0, 0 }, { -1, -1, -1, -1 }, { -1, 43200 } };
|
||||
|
||||
static struct {
|
||||
double act; /* actual load (reported by UPS) */
|
||||
double low; /* idle load */
|
||||
double eff; /* effective load */
|
||||
} load = { 0, 0.1, 1 };
|
||||
|
||||
static time_t lastpoll = 0;
|
||||
|
||||
/*
|
||||
* This little structure defines the various flavors of the Megatec protocol.
|
||||
* Only the .name and .status are mandatory, .rating and .vendor elements are
|
||||
* optional. If only some models support the last two, fill them in anyway
|
||||
* and tell people to use the 'norating' and 'novendor' options to bypass
|
||||
* getting them.
|
||||
*/
|
||||
static const struct {
|
||||
const char *name;
|
||||
const char *status;
|
||||
const char *rating;
|
||||
const char *vendor;
|
||||
} command[] = {
|
||||
{ "megatec", "Q1\r", "F\r", "I\r" },
|
||||
{ "mustek", "QS\r", "F\r", "I\r" },
|
||||
{ "megatec/old", "D\r", "F\r", "I\r" },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Do whatever we think is needed when we read a battery voltage from the UPS.
|
||||
* Basically all it does now, is guestimating the battery charge, but this
|
||||
* could be extended.
|
||||
*/
|
||||
static double blazer_battery(const char *ptr, char **endptr)
|
||||
{
|
||||
batt.volt.act = batt.packs * strtod(ptr, endptr);
|
||||
|
||||
if ((!getval("runtimecal") || !dstate_getinfo("battery.charge")) &&
|
||||
(batt.volt.low > 0) && (batt.volt.high > batt.volt.low)) {
|
||||
batt.chrg.act = 100 * (batt.volt.act - batt.volt.low) / (batt.volt.high - batt.volt.low);
|
||||
|
||||
if (batt.chrg.act < 0) {
|
||||
batt.chrg.act = 0;
|
||||
}
|
||||
|
||||
if (batt.chrg.act > 100) {
|
||||
batt.chrg.act = 100;
|
||||
}
|
||||
|
||||
dstate_setinfo("battery.charge", "%.0f", batt.chrg.act);
|
||||
}
|
||||
|
||||
return batt.volt.act;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Do whatever we think is needed when we read the load from the UPS.
|
||||
*/
|
||||
static double blazer_load(const char *ptr, char **endptr)
|
||||
{
|
||||
load.act = strtod(ptr, endptr);
|
||||
|
||||
load.eff = pow(load.act / 100, batt.runt.exp);
|
||||
|
||||
if (load.eff < load.low) {
|
||||
load.eff = load.low;
|
||||
}
|
||||
|
||||
return load.act;
|
||||
}
|
||||
|
||||
/*
|
||||
* The battery voltage will quickly return to at least the nominal value after
|
||||
* discharging them. For overlapping battery.voltage.low/high ranges therefor
|
||||
* choose the one with the highest multiplier.
|
||||
*/
|
||||
static double blazer_packs(const char *ptr, char **endptr)
|
||||
{
|
||||
const double packs[] = {
|
||||
120, 100, 80, 60, 48, 36, 30, 24, 18, 12, 8, 6, 4, 3, 2, 1, 0.5, -1
|
||||
};
|
||||
|
||||
const char *val;
|
||||
int i;
|
||||
|
||||
val = dstate_getinfo("battery.voltage.nominal");
|
||||
|
||||
batt.volt.nom = strtod(val ? val : ptr, endptr);
|
||||
|
||||
for (i = 0; packs[i] > 0; i++) {
|
||||
|
||||
if (packs[i] * batt.volt.act > 1.2 * batt.volt.nom) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (packs[i] * batt.volt.act < 0.8 * batt.volt.nom) {
|
||||
upslogx(LOG_INFO, "Can't autodetect number of battery packs [%.0f/%.2f]", batt.volt.nom, batt.volt.act);
|
||||
break;
|
||||
}
|
||||
|
||||
batt.packs = packs[i];
|
||||
break;
|
||||
}
|
||||
|
||||
return batt.volt.nom;
|
||||
}
|
||||
|
||||
|
||||
static int blazer_status(const char *cmd)
|
||||
{
|
||||
const struct {
|
||||
const char *var;
|
||||
const char *fmt;
|
||||
double (*conv)(const char *, char **);
|
||||
} status[] = {
|
||||
{ "input.voltage", "%.1f", strtod },
|
||||
{ "input.voltage.fault", "%.1f", strtod },
|
||||
{ "output.voltage", "%.1f", strtod },
|
||||
{ "ups.load", "%.0f", blazer_load },
|
||||
{ "input.frequency", "%.1f", strtod },
|
||||
{ "battery.voltage", "%.2f", blazer_battery },
|
||||
{ "ups.temperature", "%.1f", strtod },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
char buf[SMALLBUF], *val, *last = NULL;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* > [Q1\r]
|
||||
* < [(226.0 195.0 226.0 014 49.0 27.5 30.0 00001000\r]
|
||||
* 01234567890123456789012345678901234567890123456
|
||||
* 0 1 2 3 4
|
||||
*/
|
||||
if (blazer_command(cmd, buf, sizeof(buf)) < 47) {
|
||||
upsdebugx(2, "%s: short reply", __func__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (buf[0] != '(') {
|
||||
upsdebugx(2, "%s: invalid start character [%02x]", __func__, buf[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0, val = strtok_r(buf+1, " ", &last); status[i].var; i++, val = strtok_r(NULL, " \r\n", &last)) {
|
||||
|
||||
if (!val) {
|
||||
upsdebugx(2, "%s: parsing failed", __func__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (strspn(val, "0123456789.") != strlen(val)) {
|
||||
upsdebugx(2, "%s: non numerical value [%s]", __func__, val);
|
||||
continue;
|
||||
}
|
||||
|
||||
dstate_setinfo(status[i].var, status[i].fmt, status[i].conv(val, NULL));
|
||||
}
|
||||
|
||||
if (strspn(val, "01") != 8) {
|
||||
upsdebugx(2, "Invalid status [%s]", val);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (val[7] == '1') { /* Beeper On */
|
||||
dstate_setinfo("beeper.status", "enabled");
|
||||
} else {
|
||||
dstate_setinfo("beeper.status", "disabled");
|
||||
}
|
||||
|
||||
if (val[4] == '1') { /* UPS Type is Standby (0 is On_line) */
|
||||
dstate_setinfo("ups.type", "offline / line interactive");
|
||||
} else {
|
||||
dstate_setinfo("ups.type", "online");
|
||||
}
|
||||
|
||||
status_init();
|
||||
|
||||
if (val[0] == '1') { /* Utility Fail (Immediate) */
|
||||
status_set("OB");
|
||||
online = 0;
|
||||
} else {
|
||||
status_set("OL");
|
||||
online = 1;
|
||||
}
|
||||
|
||||
if (val[1] == '1') { /* Battery Low */
|
||||
status_set("LB");
|
||||
}
|
||||
|
||||
if (val[2] == '1') { /* Bypass/Boost or Buck Active */
|
||||
|
||||
double vi, vo;
|
||||
|
||||
vi = strtod(dstate_getinfo("input.voltage"), NULL);
|
||||
vo = strtod(dstate_getinfo("output.voltage"), NULL);
|
||||
|
||||
if (vo < 0.5 * vi) {
|
||||
upsdebugx(2, "%s: output voltage too low", __func__);
|
||||
} else if (vo < 0.95 * vi) {
|
||||
status_set("TRIM");
|
||||
} else if (vo < 1.05 * vi) {
|
||||
status_set("BYPASS");
|
||||
} else if (vo < 1.5 * vi) {
|
||||
status_set("BOOST");
|
||||
} else {
|
||||
upsdebugx(2, "%s: output voltage too high", __func__);
|
||||
}
|
||||
}
|
||||
|
||||
if (val[5] == '1') { /* Test in Progress */
|
||||
status_set("CAL");
|
||||
}
|
||||
|
||||
alarm_init();
|
||||
|
||||
if (val[3] == '1') { /* UPS Failed */
|
||||
alarm_set("UPS selftest failed!");
|
||||
}
|
||||
|
||||
if (val[6] == '1') { /* Shutdown Active */
|
||||
alarm_set("Shutdown imminent!");
|
||||
}
|
||||
|
||||
alarm_commit();
|
||||
|
||||
status_commit();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int blazer_rating(const char *cmd)
|
||||
{
|
||||
const struct {
|
||||
const char *var;
|
||||
const char *fmt;
|
||||
double (*conv)(const char *, char **);
|
||||
} rating[] = {
|
||||
{ "input.voltage.nominal", "%.0f", strtod },
|
||||
{ "input.current.nominal", "%.1f", strtod },
|
||||
{ "battery.voltage.nominal", "%.1f", blazer_packs },
|
||||
{ "input.frequency.nominal", "%.0f", strtod },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
char buf[SMALLBUF], *val, *last = NULL;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* > [F\r]
|
||||
* < [#220.0 000 024.0 50.0\r]
|
||||
* 0123456789012345678901
|
||||
* 0 1 2
|
||||
*/
|
||||
if (blazer_command(cmd, buf, sizeof(buf)) < 22) {
|
||||
upsdebugx(2, "%s: short reply", __func__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (buf[0] != '#') {
|
||||
upsdebugx(2, "%s: invalid start character [%02x]", __func__, buf[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0, val = strtok_r(buf+1, " ", &last); rating[i].var; i++, val = strtok_r(NULL, " \r\n", &last)) {
|
||||
|
||||
if (!val) {
|
||||
upsdebugx(2, "%s: parsing failed", __func__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (strspn(val, "0123456789.") != strlen(val)) {
|
||||
upsdebugx(2, "%s: non numerical value [%s]", __func__, val);
|
||||
continue;
|
||||
}
|
||||
|
||||
dstate_setinfo(rating[i].var, rating[i].fmt, rating[i].conv(val, NULL));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int blazer_vendor(const char *cmd)
|
||||
{
|
||||
const struct {
|
||||
const char *var;
|
||||
const int len;
|
||||
} information[] = {
|
||||
{ "ups.mfr", 15 },
|
||||
{ "ups.model", 10 },
|
||||
{ "ups.firmware", 10 },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
char buf[SMALLBUF];
|
||||
int i, index;
|
||||
|
||||
/*
|
||||
* > [I\r]
|
||||
* < [#------------- ------ VT12046Q \r]
|
||||
* 012345678901234567890123456789012345678
|
||||
* 0 1 2 3
|
||||
*/
|
||||
if (blazer_command(cmd, buf, sizeof(buf)) < 39) {
|
||||
upsdebugx(2, "%s: short reply", __func__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (buf[0] != '#') {
|
||||
upsdebugx(2, "%s: invalid start character [%02x]", __func__, buf[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0, index = 1; information[i].var; index += information[i++].len+1) {
|
||||
char val[SMALLBUF];
|
||||
|
||||
snprintf(val, sizeof(val), "%.*s", information[i].len, &buf[index]);
|
||||
|
||||
dstate_setinfo(information[i].var, "%s", rtrim(val, ' '));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int blazer_instcmd(const char *cmdname, const char *extra)
|
||||
{
|
||||
const struct {
|
||||
const char *cmd;
|
||||
const char *ups;
|
||||
} instcmd[] = {
|
||||
{ "beeper.toggle", "Q\r" },
|
||||
{ "load.off", "S00R0000\r" },
|
||||
{ "load.on", "C\r" },
|
||||
{ "shutdown.stop", "C\r" },
|
||||
{ "test.battery.start.deep", "TL\r" },
|
||||
{ "test.battery.start.quick", "T\r" },
|
||||
{ "test.battery.stop", "CT\r" },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
char buf[SMALLBUF] = "";
|
||||
int i;
|
||||
|
||||
for (i = 0; instcmd[i].cmd; i++) {
|
||||
|
||||
if (strcasecmp(cmdname, instcmd[i].cmd)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s", instcmd[i].ups);
|
||||
|
||||
/*
|
||||
* If a command is invalid, it will be echoed back
|
||||
*/
|
||||
if (blazer_command(buf, buf, sizeof(buf)) > 0) {
|
||||
upslogx(LOG_ERR, "instcmd: command [%s] failed", cmdname);
|
||||
return STAT_INSTCMD_FAILED;
|
||||
}
|
||||
|
||||
upslogx(LOG_INFO, "instcmd: command [%s] handled", cmdname);
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
if (!strcasecmp(cmdname, "shutdown.return")) {
|
||||
if (offdelay < 60) {
|
||||
snprintf(buf, sizeof(buf), "S.%dR%04d\r", offdelay / 6, ondelay);
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "S%02dR%04d\r", offdelay / 60, ondelay);
|
||||
}
|
||||
} else if (!strcasecmp(cmdname, "shutdown.stayoff")) {
|
||||
if (offdelay < 60) {
|
||||
snprintf(buf, sizeof(buf), "S.%dR0000\r", offdelay / 6);
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "S%02dR0000\r", offdelay / 60);
|
||||
}
|
||||
} else if (!strcasecmp(cmdname, "test.battery.start")) {
|
||||
int delay = extra ? strtol(extra, NULL, 10) : 10;
|
||||
|
||||
if ((delay < 0) || (delay > 99)) {
|
||||
return STAT_INSTCMD_FAILED;
|
||||
}
|
||||
|
||||
snprintf(buf, sizeof(buf), "T%02d\r", delay);
|
||||
} else {
|
||||
upslogx(LOG_ERR, "instcmd: command [%s] not found", cmdname);
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
}
|
||||
|
||||
/*
|
||||
* If a command is invalid, it will be echoed back
|
||||
*/
|
||||
if (blazer_command(buf, buf, sizeof(buf)) > 0) {
|
||||
upslogx(LOG_ERR, "instcmd: command [%s] failed", cmdname);
|
||||
return STAT_INSTCMD_FAILED;
|
||||
}
|
||||
|
||||
upslogx(LOG_INFO, "instcmd: command [%s] handled", cmdname);
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
|
||||
void blazer_makevartable(void)
|
||||
{
|
||||
addvar(VAR_VALUE, "ondelay", "Delay before UPS startup (minutes)");
|
||||
addvar(VAR_VALUE, "offdelay", "Delay before UPS shutdown (seconds)");
|
||||
|
||||
addvar(VAR_VALUE, "runtimecal", "Parameters used for runtime calculation");
|
||||
addvar(VAR_VALUE, "chargetime", "Nominal charge time for UPS battery");
|
||||
addvar(VAR_VALUE, "idleload", "Minimum load to be used for runtime calculation");
|
||||
|
||||
addvar(VAR_FLAG, "norating", "Skip reading rating information from UPS");
|
||||
addvar(VAR_FLAG, "novendor", "Skip reading vendor information from UPS");
|
||||
}
|
||||
|
||||
|
||||
void blazer_initups(void)
|
||||
{
|
||||
const char *val;
|
||||
|
||||
val = getval("ondelay");
|
||||
if (val) {
|
||||
ondelay = strtol(val, NULL, 10);
|
||||
}
|
||||
|
||||
if ((ondelay < 0) || (ondelay > 9999)) {
|
||||
fatalx(EXIT_FAILURE, "Start delay '%d' out of range [0..9999]", ondelay);
|
||||
}
|
||||
|
||||
val = getval("offdelay");
|
||||
if (val) {
|
||||
offdelay = strtol(val, NULL, 10);
|
||||
}
|
||||
|
||||
if ((offdelay < 6) || (offdelay > 600)) {
|
||||
fatalx(EXIT_FAILURE, "Shutdown delay '%d' out of range [6..600]", offdelay);
|
||||
}
|
||||
|
||||
/* Truncate to nearest setable value */
|
||||
if (offdelay < 60) {
|
||||
offdelay -= (offdelay % 6);
|
||||
} else {
|
||||
offdelay -= (offdelay % 60);
|
||||
}
|
||||
|
||||
val = dstate_getinfo("battery.voltage.high");
|
||||
if (val) {
|
||||
batt.volt.high = strtod(val, NULL);
|
||||
}
|
||||
|
||||
val = dstate_getinfo("battery.voltage.low");
|
||||
if (val) {
|
||||
batt.volt.low = strtod(val, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void blazer_initbattery(void)
|
||||
{
|
||||
const char *val;
|
||||
|
||||
val = getval("runtimecal");
|
||||
if (val) {
|
||||
int rh, lh, rl, ll;
|
||||
|
||||
time(&lastpoll);
|
||||
|
||||
if (sscanf(val, "%d,%d,%d,%d", &rh, &lh, &rl, &ll) < 4) {
|
||||
fatalx(EXIT_FAILURE, "Insufficient parameters for runtimecal");
|
||||
}
|
||||
|
||||
if ((rl < rh) || (rh <= 0)) {
|
||||
fatalx(EXIT_FAILURE, "Parameter out of range (runtime)");
|
||||
}
|
||||
|
||||
if ((lh > 100) || (ll > lh) || (ll <= 0)) {
|
||||
fatalx(EXIT_FAILURE, "Parameter out of range (load)");
|
||||
}
|
||||
|
||||
batt.runt.exp = log((double)rl / rh) / log((double)lh / ll);
|
||||
upsdebugx(2, "battery runtime exponent : %.3f", batt.runt.exp);
|
||||
|
||||
batt.runt.nom = rh * pow(lh/100, batt.runt.exp);
|
||||
upsdebugx(2, "battery runtime nominal : %.1f", batt.runt.nom);
|
||||
|
||||
} else {
|
||||
upslogx(LOG_INFO, "Battery runtime will not be calculated (runtimecal not set)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (batt.chrg.act < 0) {
|
||||
batt.volt.low = batt.volt.nom;
|
||||
batt.volt.high = 1.15 * batt.volt.nom;
|
||||
|
||||
blazer_battery(dstate_getinfo("battery.voltage"), NULL);
|
||||
}
|
||||
|
||||
val = dstate_getinfo("battery.charge");
|
||||
if (val) {
|
||||
batt.runt.est = batt.runt.nom * strtod(val, NULL) / 100;
|
||||
upsdebugx(2, "battery runtime estimate : %.1f", batt.runt.est);
|
||||
} else {
|
||||
fatalx(EXIT_FAILURE, "Initial battery charge undetermined");
|
||||
}
|
||||
|
||||
val = getval("chargetime");
|
||||
if (val) {
|
||||
batt.chrg.time = strtol(val, NULL, 10);
|
||||
|
||||
if (batt.chrg.time <= 0) {
|
||||
fatalx(EXIT_FAILURE, "Charge time out of range [1..s]");
|
||||
}
|
||||
|
||||
upsdebugx(2, "battery charge time : %ld", batt.chrg.time);
|
||||
} else {
|
||||
upslogx(LOG_INFO, "No charge time specified, using built in default [%ld seconds]", batt.chrg.time);
|
||||
}
|
||||
|
||||
val = getval("idleload");
|
||||
if (val) {
|
||||
load.low = strtod(val, NULL) / 100;
|
||||
|
||||
if ((load.low <= 0) || (load.low > 1)) {
|
||||
fatalx(EXIT_FAILURE, "Idle load out of range [0..100]");
|
||||
}
|
||||
|
||||
upsdebugx(2, "minimum load used (idle) : %.3f", load.low);
|
||||
} else {
|
||||
upslogx(LOG_INFO, "No idle load specified, using built in default [%.1f %%]", 100 * load.low);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void blazer_initinfo(void)
|
||||
{
|
||||
int retry;
|
||||
|
||||
for (proto = 0; command[proto].status; proto++) {
|
||||
|
||||
int ret;
|
||||
|
||||
upsdebugx(2, "Trying %s protocol...", command[proto].name);
|
||||
|
||||
for (retry = 1; retry <= MAXTRIES; retry++) {
|
||||
|
||||
ret = blazer_status(command[proto].status);
|
||||
if (ret < 0) {
|
||||
upsdebugx(2, "Status read %d failed", retry);
|
||||
continue;
|
||||
}
|
||||
|
||||
upsdebugx(2, "Status read in %d tries", retry);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ret) {
|
||||
upslogx(LOG_INFO, "Supported UPS detected with %s protocol", command[proto].name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!command[proto].status) {
|
||||
fatalx(EXIT_FAILURE, "No supported UPS detected");
|
||||
}
|
||||
|
||||
if (command[proto].rating && !testvar("norating")) {
|
||||
int ret;
|
||||
|
||||
for (retry = 1; retry <= MAXTRIES; retry++) {
|
||||
|
||||
ret = blazer_rating(command[proto].rating);
|
||||
if (ret < 0) {
|
||||
upsdebugx(1, "Rating read %d failed", retry);
|
||||
continue;
|
||||
}
|
||||
|
||||
upsdebugx(2, "Ratings read in %d tries", retry);
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
upslogx(LOG_DEBUG, "Rating information unavailable");
|
||||
}
|
||||
}
|
||||
|
||||
if (command[proto].vendor && !testvar("novendor")) {
|
||||
int ret;
|
||||
|
||||
for (retry = 1; retry <= MAXTRIES; retry++) {
|
||||
|
||||
ret = blazer_vendor(command[proto].vendor);
|
||||
if (ret < 0) {
|
||||
upsdebugx(1, "Vendor information read %d failed", retry);
|
||||
continue;
|
||||
}
|
||||
|
||||
upslogx(LOG_INFO, "Vendor information read in %d tries", retry);
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
upslogx(LOG_DEBUG, "Vendor information unavailable");
|
||||
}
|
||||
}
|
||||
|
||||
blazer_initbattery();
|
||||
|
||||
dstate_setinfo("ups.delay.start", "%d", 60 * ondelay);
|
||||
dstate_setinfo("ups.delay.shutdown", "%d", offdelay);
|
||||
|
||||
dstate_addcmd("beeper.toggle");
|
||||
dstate_addcmd("load.off");
|
||||
dstate_addcmd("load.on");
|
||||
dstate_addcmd("shutdown.return");
|
||||
dstate_addcmd("shutdown.stayoff");
|
||||
dstate_addcmd("shutdown.stop");
|
||||
dstate_addcmd("test.battery.start");
|
||||
dstate_addcmd("test.battery.start.deep");
|
||||
dstate_addcmd("test.battery.start.quick");
|
||||
dstate_addcmd("test.battery.stop");
|
||||
|
||||
upsh.instcmd = blazer_instcmd;
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
static int retry = 0;
|
||||
|
||||
if (blazer_status(command[proto].status)) {
|
||||
|
||||
if (retry < MAXTRIES) {
|
||||
upslogx(LOG_WARNING, "Communications with UPS lost: status read failed!");
|
||||
retry++;
|
||||
} else {
|
||||
dstate_datastale();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (getval("runtimecal")) {
|
||||
time_t now;
|
||||
|
||||
time(&now);
|
||||
|
||||
if (online) { /* OL */
|
||||
batt.runt.est += batt.runt.nom * difftime(now, lastpoll) / batt.chrg.time;
|
||||
if (batt.runt.est > batt.runt.nom) {
|
||||
batt.runt.est = batt.runt.nom;
|
||||
}
|
||||
} else { /* OB */
|
||||
batt.runt.est -= load.eff * difftime(now, lastpoll);
|
||||
if (batt.runt.est < 0) {
|
||||
batt.runt.est = 0;
|
||||
}
|
||||
}
|
||||
|
||||
dstate_setinfo("battery.charge", "%.0f", 100 * batt.runt.est / batt.runt.nom);
|
||||
dstate_setinfo("battery.runtime", "%.0f", batt.runt.est / load.eff);
|
||||
|
||||
lastpoll = now;
|
||||
}
|
||||
|
||||
if (retry) {
|
||||
upslogx(LOG_NOTICE, "Communications with UPS re-established");
|
||||
}
|
||||
|
||||
retry = 0;
|
||||
|
||||
dstate_dataok();
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
int retry;
|
||||
|
||||
for (retry = 1; retry <= MAXTRIES; retry++) {
|
||||
|
||||
if (blazer_instcmd("shutdown.stop", NULL) != STAT_INSTCMD_HANDLED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (blazer_instcmd("shutdown.return", NULL) != STAT_INSTCMD_HANDLED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
fatalx(EXIT_SUCCESS, "Shutting down in %d seconds", offdelay);
|
||||
}
|
||||
|
||||
fatalx(EXIT_FAILURE, "Shutdown failed!");
|
||||
}
|
||||
49
drivers/blazer.h
Normal file
49
drivers/blazer.h
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* blazer.h: defines/macros for Megatec/Q1 protocol based UPSes
|
||||
*
|
||||
* A document describing the protocol implemented by this driver can be
|
||||
* found online at "http://www.networkupstools.org/protocols/megatec.html".
|
||||
*
|
||||
* Copyright (C) 2008 - Arjen de Korte <adkorte-guest@alioth.debian.org>
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef BLAZER_H
|
||||
#define BLAZER_H
|
||||
|
||||
#define MAXTRIES 3
|
||||
|
||||
/*
|
||||
* The driver core will do all the protocol handling and provides
|
||||
* the following (interface independent) parts already
|
||||
*
|
||||
* upsdrv_updateinfo()
|
||||
* upsdrv_shutdown()
|
||||
*
|
||||
* Communication with the UPS is done through blazer_command() of which
|
||||
* the prototype is declared below. It shall send a command and reads
|
||||
* a reply if buf is not a NULL pointer and buflen > 0.
|
||||
*
|
||||
* Returns < 0 on error, 0 on timeout and the number of bytes send/read on
|
||||
* success.
|
||||
*/
|
||||
int blazer_command(const char *cmd, char *buf, size_t buflen);
|
||||
|
||||
void blazer_makevartable(void);
|
||||
void blazer_initups(void);
|
||||
void blazer_initinfo(void);
|
||||
|
||||
#endif /* BLAZER_H */
|
||||
200
drivers/blazer_ser.c
Normal file
200
drivers/blazer_ser.c
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* blazer_ser.c: support for Megatec/Q1 serial protocol based UPSes
|
||||
*
|
||||
* A document describing the protocol implemented by this driver can be
|
||||
* found online at "http://www.networkupstools.org/protocols/megatec.html".
|
||||
*
|
||||
* Copyright (C) 2008 - Arjen de Korte <adkorte-guest@alioth.debian.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "serial.h"
|
||||
#include "blazer.h"
|
||||
|
||||
#define DRIVER_NAME "Megatec/Q1 protocol serial driver"
|
||||
#define DRIVER_VERSION "1.51"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Arjen de Korte <adkorte-guest@alioth.debian.org>",
|
||||
DRV_BETA,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
#define SER_WAIT_SEC 1
|
||||
|
||||
/*
|
||||
* Generic command processing function. Send a command and read a reply.
|
||||
* Returns < 0 on error, 0 on timeout and the number of bytes read on
|
||||
* success.
|
||||
*/
|
||||
int blazer_command(const char *cmd, char *buf, size_t buflen)
|
||||
{
|
||||
#ifndef TESTING
|
||||
int ret;
|
||||
|
||||
ser_flush_io(upsfd);
|
||||
|
||||
ret = ser_send(upsfd, "%s", cmd);
|
||||
|
||||
if (ret <= 0) {
|
||||
upsdebugx(3, "send: %s", ret ? strerror(errno) : "timeout");
|
||||
return ret;
|
||||
}
|
||||
|
||||
upsdebugx(3, "send: %.*s", (int)strcspn(cmd, "\r"), cmd);
|
||||
|
||||
ret = ser_get_buf(upsfd, buf, buflen, SER_WAIT_SEC, 0);
|
||||
|
||||
if (ret <= 0) {
|
||||
upsdebugx(3, "read: %s", ret ? strerror(errno) : "timeout");
|
||||
return ret;
|
||||
}
|
||||
|
||||
upsdebugx(3, "read: %.*s", (int)strcspn(buf, "\r"), buf);
|
||||
return ret;
|
||||
#else
|
||||
const struct {
|
||||
const char *cmd;
|
||||
const char *answer;
|
||||
} testing[] = {
|
||||
{ "Q1\r", "(215.0 195.0 230.0 014 49.0 2.27 30.0 00101000\r" },
|
||||
{ "F\r", "#230.0 000 024.0 50.0\r" },
|
||||
{ "I\r", "#NOT_A_LIVE_UPS TESTING TESTING \r" },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
int i;
|
||||
|
||||
memset(buf, 0, buflen);
|
||||
|
||||
for (i = 0; cmd && testing[i].cmd; i++) {
|
||||
|
||||
if (strcasecmp(cmd, testing[i].cmd)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return snprintf(buf, buflen, "%s", testing[i].answer);
|
||||
}
|
||||
|
||||
return snprintf(buf, buflen, "%s", testing[i].cmd);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
printf("Read The Fine Manual ('man 8 blazer')\n");
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
addvar(VAR_VALUE, "cablepower", "Set cable power for serial interface");
|
||||
|
||||
blazer_makevartable();
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
const struct {
|
||||
const char *val;
|
||||
const int dtr;
|
||||
const int rts;
|
||||
} cablepower[] = {
|
||||
{ "normal", 1, 0 }, /* default */
|
||||
{ "reverse", 0, 1 },
|
||||
{ "both", 1, 1 },
|
||||
{ "none", 0, 0 },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
int i;
|
||||
|
||||
const char *val;
|
||||
#ifndef TESTING
|
||||
struct termios tio;
|
||||
|
||||
/*
|
||||
* Open and lock the serial port and set the speed to 2400 baud.
|
||||
*/
|
||||
upsfd = ser_open(device_path);
|
||||
ser_set_speed(upsfd, device_path, B2400);
|
||||
|
||||
if (tcgetattr(upsfd, &tio)) {
|
||||
fatal_with_errno(EXIT_FAILURE, "tcgetattr");
|
||||
}
|
||||
|
||||
/*
|
||||
* Use canonical mode input processing (to read reply line)
|
||||
*/
|
||||
tio.c_lflag |= ICANON; /* Canonical input (erase and kill processing) */
|
||||
|
||||
tio.c_cc[VEOF] = _POSIX_VDISABLE;
|
||||
tio.c_cc[VEOL] = '\r';
|
||||
tio.c_cc[VERASE] = _POSIX_VDISABLE;
|
||||
tio.c_cc[VINTR] = _POSIX_VDISABLE;
|
||||
tio.c_cc[VKILL] = _POSIX_VDISABLE;
|
||||
tio.c_cc[VQUIT] = _POSIX_VDISABLE;
|
||||
tio.c_cc[VSUSP] = _POSIX_VDISABLE;
|
||||
tio.c_cc[VSTART] = _POSIX_VDISABLE;
|
||||
tio.c_cc[VSTOP] = _POSIX_VDISABLE;
|
||||
|
||||
if (tcsetattr(upsfd, TCSANOW, &tio)) {
|
||||
fatal_with_errno(EXIT_FAILURE, "tcsetattr");
|
||||
}
|
||||
|
||||
val = getval("cablepower");
|
||||
for (i = 0; val && cablepower[i].val; i++) {
|
||||
|
||||
if (!strcasecmp(val, cablepower[i].val)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cablepower[i].val) {
|
||||
fatalx(EXIT_FAILURE, "Value '%s' not valid for 'cablepower'", val);
|
||||
}
|
||||
|
||||
ser_set_dtr(upsfd, cablepower[i].dtr);
|
||||
ser_set_rts(upsfd, cablepower[i].rts);
|
||||
|
||||
/*
|
||||
* Allow some time to settle for the cablepower
|
||||
*/
|
||||
usleep(100000);
|
||||
#endif
|
||||
blazer_initups();
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_initinfo(void)
|
||||
{
|
||||
blazer_initinfo();
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
#ifndef TESTING
|
||||
ser_set_dtr(upsfd, 0);
|
||||
ser_close(upsfd, device_path);
|
||||
#endif
|
||||
}
|
||||
558
drivers/blazer_usb.c
Normal file
558
drivers/blazer_usb.c
Normal file
|
|
@ -0,0 +1,558 @@
|
|||
/*
|
||||
* blazer_usb.c: support for Megatec/Q1 USB protocol based UPSes
|
||||
*
|
||||
* A document describing the protocol implemented by this driver can be
|
||||
* found online at "http://www.networkupstools.org/protocols/megatec.html".
|
||||
*
|
||||
* Copyright (C) 2003-2009 Arjen de Korte <adkorte-guest@alioth.debian.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "libusb.h"
|
||||
#include "usb-common.h"
|
||||
#include "blazer.h"
|
||||
|
||||
#define DRIVER_NAME "Megatec/Q1 protocol USB driver"
|
||||
#define DRIVER_VERSION "0.03"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Arjen de Korte <adkorte-guest@alioth.debian.org>",
|
||||
DRV_BETA,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static usb_communication_subdriver_t *usb = &usb_subdriver;
|
||||
static usb_dev_handle *udev = NULL;
|
||||
static USBDevice_t usbdevice;
|
||||
static USBDeviceMatcher_t *reopen_matcher = NULL;
|
||||
static USBDeviceMatcher_t *regex_matcher = NULL;
|
||||
|
||||
static int (*subdriver_command)(const char *cmd, char *buf, size_t buflen) = NULL;
|
||||
|
||||
|
||||
static int cypress_command(const char *cmd, char *buf, size_t buflen)
|
||||
{
|
||||
char tmp[SMALLBUF];
|
||||
int ret;
|
||||
size_t i;
|
||||
|
||||
memset(tmp, 0, sizeof(tmp));
|
||||
snprintf(tmp, sizeof(tmp), "%s", cmd);
|
||||
|
||||
for (i = 0; i < strlen(tmp); i += ret) {
|
||||
|
||||
/* Write data in 8-byte chunks */
|
||||
/* ret = usb->set_report(udev, 0, (unsigned char *)&tmp[i], 8); */
|
||||
ret = usb_control_msg(udev, USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE,
|
||||
0x09, 0x200, 0, &tmp[i], 8, 1000);
|
||||
|
||||
if (ret <= 0) {
|
||||
upsdebugx(3, "send: %s", ret ? usb_strerror() : "timeout");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
upsdebugx(3, "send: %.*s", (int)strcspn(tmp, "\r"), tmp);
|
||||
|
||||
memset(buf, 0, buflen);
|
||||
|
||||
for (i = 0; (i <= buflen-8) && (strchr(buf, '\r') == NULL); i += ret) {
|
||||
|
||||
/* Read data in 8-byte chunks */
|
||||
/* ret = usb->get_interrupt(udev, (unsigned char *)&buf[i], 8, 1000); */
|
||||
ret = usb_interrupt_read(udev, 0x81, &buf[i], 8, 1000);
|
||||
|
||||
/*
|
||||
* Any errors here mean that we are unable to read a reply (which
|
||||
* will happen after successfully writing a command to the UPS)
|
||||
*/
|
||||
if (ret <= 0) {
|
||||
upsdebugx(3, "read: %s", ret ? usb_strerror() : "timeout");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
upsdebugx(3, "read: %.*s", (int)strcspn(buf, "\r"), buf);
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
static int phoenix_command(const char *cmd, char *buf, size_t buflen)
|
||||
{
|
||||
char tmp[SMALLBUF];
|
||||
int ret;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
|
||||
/* Read data in 8-byte chunks */
|
||||
/* ret = usb->get_interrupt(udev, (unsigned char *)tmp, 8, 1000); */
|
||||
ret = usb_interrupt_read(udev, 0x81, tmp, 8, 1000);
|
||||
|
||||
/*
|
||||
* This USB to serial implementation is crappy. In order to read correct
|
||||
* replies we need to flush the output buffers of the converter until we
|
||||
* get no more data (ie, it times out).
|
||||
*/
|
||||
switch (ret)
|
||||
{
|
||||
case -EPIPE: /* Broken pipe */
|
||||
usb_clear_halt(udev, 0x81);
|
||||
case -ETIMEDOUT: /* Connection timed out */
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
upsdebugx(3, "flush: %s", usb_strerror());
|
||||
break;
|
||||
}
|
||||
|
||||
upsdebug_hex(4, "dump", tmp, ret);
|
||||
}
|
||||
|
||||
memset(tmp, 0, sizeof(tmp));
|
||||
snprintf(tmp, sizeof(tmp), "%s", cmd);
|
||||
|
||||
for (i = 0; i < strlen(tmp); i += ret) {
|
||||
|
||||
/* Write data in 8-byte chunks */
|
||||
/* ret = usb->set_report(udev, 0, (unsigned char *)&tmp[i], 8); */
|
||||
ret = usb_control_msg(udev, USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE,
|
||||
0x09, 0x200, 0, &tmp[i], 8, 1000);
|
||||
|
||||
if (ret <= 0) {
|
||||
upsdebugx(3, "send: %s", ret ? usb_strerror() : "timeout");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
upsdebugx(3, "send: %.*s", (int)strcspn(tmp, "\r"), tmp);
|
||||
|
||||
memset(buf, 0, buflen);
|
||||
|
||||
for (i = 0; (i <= buflen-8) && (strchr(buf, '\r') == NULL); i += ret) {
|
||||
|
||||
/* Read data in 8-byte chunks */
|
||||
/* ret = usb->get_interrupt(udev, (unsigned char *)&buf[i], 8, 1000); */
|
||||
ret = usb_interrupt_read(udev, 0x81, &buf[i], 8, 1000);
|
||||
|
||||
/*
|
||||
* Any errors here mean that we are unable to read a reply (which
|
||||
* will happen after successfully writing a command to the UPS)
|
||||
*/
|
||||
if (ret <= 0) {
|
||||
upsdebugx(3, "read: %s", ret ? usb_strerror() : "timeout");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
upsdebugx(3, "read: %.*s", (int)strcspn(buf, "\r"), buf);
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
static int ippon_command(const char *cmd, char *buf, size_t buflen)
|
||||
{
|
||||
char tmp[64];
|
||||
int ret;
|
||||
size_t i;
|
||||
|
||||
snprintf(tmp, sizeof(tmp), "%s", cmd);
|
||||
|
||||
for (i = 0; i < strlen(tmp); i += ret) {
|
||||
|
||||
/* Write data in 8-byte chunks */
|
||||
ret = usb_control_msg(udev, USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE,
|
||||
0x09, 0x2, 0, &tmp[i], 8, 1000);
|
||||
|
||||
if (ret <= 0) {
|
||||
upsdebugx(3, "send: %s", (ret != -ETIMEDOUT) ? usb_strerror() : "Connection timed out");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
upsdebugx(3, "send: %.*s", (int)strcspn(tmp, "\r"), tmp);
|
||||
|
||||
/* Read all 64 bytes of the reply in one large chunk */
|
||||
ret = usb_interrupt_read(udev, 0x81, tmp, sizeof(tmp), 1000);
|
||||
|
||||
/*
|
||||
* Any errors here mean that we are unable to read a reply (which
|
||||
* will happen after successfully writing a command to the UPS)
|
||||
*/
|
||||
if (ret <= 0) {
|
||||
upsdebugx(3, "read: %s", (ret != -ETIMEDOUT) ? usb_strerror() : "Connection timed out");
|
||||
return ret;
|
||||
}
|
||||
|
||||
snprintf(buf, buflen, "%.*s", ret, tmp);
|
||||
|
||||
upsdebugx(3, "read: %.*s", (int)strcspn(buf, "\r"), buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static int krauler_command(const char *cmd, char *buf, size_t buflen)
|
||||
{
|
||||
/*
|
||||
* Still not implemented:
|
||||
* 0x6 T<n> (don't know how to pass the parameter)
|
||||
* 0x68 and 0x69 both cause shutdown after an undefined interval
|
||||
*/
|
||||
const struct {
|
||||
const char *str; /* Megatec command */
|
||||
const int index; /* Krauler string index for this command */
|
||||
const char prefix; /* character to replace the first byte in reply */
|
||||
} command[] = {
|
||||
{ "Q1\r", 0x03, '(' },
|
||||
{ "F\r", 0x0d, '#' },
|
||||
{ "I\r", 0x0c, '#' },
|
||||
{ "T\r", 0x04, '\r' },
|
||||
{ "TL\r", 0x05, '\r' },
|
||||
{ "Q\r", 0x07, '\r' },
|
||||
{ "C\r", 0x0b, '\r' },
|
||||
{ "CT\r", 0x0b, '\r' },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
int i;
|
||||
|
||||
upsdebugx(3, "send: %.*s", (int)strcspn(cmd, "\r"), cmd);
|
||||
|
||||
for (i = 0; command[i].str; i++) {
|
||||
int retry;
|
||||
|
||||
if (strcmp(cmd, command[i].str)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (retry = 0; retry < 10; retry++) {
|
||||
int ret;
|
||||
|
||||
ret = usb_get_string_simple(udev, command[i].index, buf, buflen);
|
||||
|
||||
if (ret <= 0) {
|
||||
upsdebugx(3, "read: %s", ret ? usb_strerror() : "timeout");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* "UPS No Ack" has a special meaning */
|
||||
if (!strcasecmp(buf, "UPS No Ack")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Replace the first byte of what we received with the correct one */
|
||||
buf[0] = command[i].prefix;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
upsdebugx(3, "read: %.*s", (int)strcspn(buf, "\r"), buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* echo the unknown command back */
|
||||
upsdebugx(3, "read: %.*s", (int)strcspn(cmd, "\r"), cmd);
|
||||
return snprintf(buf, buflen, "%s", cmd);
|
||||
}
|
||||
|
||||
|
||||
static void *cypress_subdriver(void)
|
||||
{
|
||||
subdriver_command = &cypress_command;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static void *ippon_subdriver(void)
|
||||
{
|
||||
subdriver_command = &ippon_command;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static void *krauler_subdriver(void)
|
||||
{
|
||||
subdriver_command = &krauler_command;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static usb_device_id_t blazer_usb_id[] = {
|
||||
{ USB_DEVICE(0x05b8, 0x0000), &cypress_subdriver }, /* Agiler UPS */
|
||||
{ USB_DEVICE(0x0001, 0x0000), &krauler_subdriver }, /* Krauler UP-M500VA */
|
||||
{ USB_DEVICE(0xffff, 0x0000), &krauler_subdriver }, /* Ablerex 625L USB */
|
||||
{ USB_DEVICE(0x0665, 0x5161), &cypress_subdriver }, /* Belkin F6C1200-UNV */
|
||||
{ USB_DEVICE(0x06da, 0x0003), &ippon_subdriver }, /* Mustek Powermust */
|
||||
{ USB_DEVICE(0x0f03, 0x0001), &cypress_subdriver }, /* Unitek Alpha 1200Sx */
|
||||
/* end of list */
|
||||
{-1, -1, NULL}
|
||||
};
|
||||
|
||||
|
||||
static int device_match_func(USBDevice_t *hd, void *privdata)
|
||||
{
|
||||
if (subdriver_command) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
switch (is_usb_device_supported(blazer_usb_id, hd->VendorID, hd->ProductID))
|
||||
{
|
||||
case SUPPORTED:
|
||||
return 1;
|
||||
|
||||
case POSSIBLY_SUPPORTED:
|
||||
case NOT_SUPPORTED:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static USBDeviceMatcher_t device_matcher = {
|
||||
&device_match_func,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Generic command processing function. Send a command and read a reply.
|
||||
* Returns < 0 on error, 0 on timeout and the number of bytes read on
|
||||
* success.
|
||||
*/
|
||||
int blazer_command(const char *cmd, char *buf, size_t buflen)
|
||||
{
|
||||
#ifndef TESTING
|
||||
int ret;
|
||||
|
||||
if (udev == NULL) {
|
||||
ret = usb->open(&udev, &usbdevice, reopen_matcher, NULL);
|
||||
|
||||
if (ret < 1) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = (*subdriver_command)(cmd, buf, buflen);
|
||||
if (ret >= 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
switch (ret)
|
||||
{
|
||||
case -EBUSY: /* Device or resource busy */
|
||||
fatal_with_errno(EXIT_FAILURE, "Got disconnected by another driver");
|
||||
|
||||
case -EPERM: /* Operation not permitted */
|
||||
fatal_with_errno(EXIT_FAILURE, "Permissions problem");
|
||||
|
||||
case -EPIPE: /* Broken pipe */
|
||||
if (usb_clear_halt(udev, 0x81) == 0) {
|
||||
upsdebugx(1, "Stall condition cleared");
|
||||
break;
|
||||
}
|
||||
#ifdef ETIME
|
||||
case -ETIME: /* Timer expired */
|
||||
#endif
|
||||
if (usb_reset(udev) == 0) {
|
||||
upsdebugx(1, "Device reset handled");
|
||||
}
|
||||
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! */
|
||||
usb->close(udev);
|
||||
udev = NULL;
|
||||
break;
|
||||
|
||||
case -ETIMEDOUT: /* Connection timed out */
|
||||
case -EOVERFLOW: /* Value too large for defined data type */
|
||||
case -EPROTO: /* Protocol error */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
#else
|
||||
const struct {
|
||||
const char *command;
|
||||
const char *answer;
|
||||
} testing[] = {
|
||||
{ "Q1\r", "(215.0 195.0 230.0 014 49.0 2.27 30.0 00101000\r" },
|
||||
{ "F\r", "#230.0 000 024.0 50.0\r" },
|
||||
{ "I\r", "#------------- ------ VT12046Q \r" },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
int i;
|
||||
|
||||
memset(buf, 0, buflen);
|
||||
|
||||
for (i = 0; testing[i].command; i++) {
|
||||
|
||||
if (strcasecmp(cmd, testing[i].command)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return snprintf(buf, buflen, "%s", testing[i].answer);
|
||||
}
|
||||
|
||||
return snprintf(buf, buflen, "%s", testing[i].command);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
printf("Read The Fine Manual ('man 8 blazer')\n");
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
addvar(VAR_VALUE, "subdriver", "Serial-over-USB subdriver selection");
|
||||
addvar(VAR_VALUE, "vendorid", "Regular expression to match UPS Manufacturer numerical ID (4 digits hexadecimal)");
|
||||
addvar(VAR_VALUE, "productid", "Regular expression to match UPS Product numerical ID (4 digits hexadecimal)");
|
||||
|
||||
addvar(VAR_VALUE, "vendor", "Regular expression to match UPS Manufacturer string");
|
||||
addvar(VAR_VALUE, "product", "Regular expression to match UPS Product string");
|
||||
addvar(VAR_VALUE, "serial", "Regular expression to match UPS Serial number");
|
||||
|
||||
addvar(VAR_VALUE, "bus", "Regular expression to match USB bus name");
|
||||
|
||||
blazer_makevartable();
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
#ifndef TESTING
|
||||
const struct {
|
||||
const char *name;
|
||||
int (*command)(const char *cmd, char *buf, size_t buflen);
|
||||
} subdriver[] = {
|
||||
{ "cypress", &cypress_command },
|
||||
{ "phoenix", &phoenix_command },
|
||||
{ "ippon", &ippon_command },
|
||||
{ "krauler", &krauler_command },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
int ret;
|
||||
char *regex_array[6];
|
||||
|
||||
char *subdrv = getval("subdriver");
|
||||
|
||||
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");
|
||||
|
||||
/* pick up the subdriver name if set explicitly */
|
||||
if (subdrv) {
|
||||
int i;
|
||||
|
||||
if (!regex_array[0] || !regex_array[1]) {
|
||||
fatalx(EXIT_FAILURE, "When specifying a subdriver, 'vendorid' and 'productid' are mandatory.");
|
||||
}
|
||||
|
||||
for (i = 0; subdriver[i].name; i++) {
|
||||
|
||||
if (strcasecmp(subdrv, subdriver[i].name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
subdriver_command = subdriver[i].command;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!subdriver_command) {
|
||||
fatalx(EXIT_FAILURE, "Subdriver \"%s\" not found!", subdrv);
|
||||
}
|
||||
}
|
||||
|
||||
ret = USBNewRegexMatcher(®ex_matcher, regex_array, REG_ICASE | REG_EXTENDED);
|
||||
switch (ret)
|
||||
{
|
||||
case -1:
|
||||
fatal_with_errno(EXIT_FAILURE, "USBNewRegexMatcher");
|
||||
case 0:
|
||||
break; /* all is well */
|
||||
default:
|
||||
fatalx(EXIT_FAILURE, "invalid regular expression: %s", regex_array[ret]);
|
||||
}
|
||||
|
||||
|
||||
/* link the matchers */
|
||||
regex_matcher->next = &device_matcher;
|
||||
|
||||
ret = usb->open(&udev, &usbdevice, regex_matcher, NULL);
|
||||
if (ret < 0) {
|
||||
fatalx(EXIT_FAILURE,
|
||||
"No supported devices found. Please check your device availability with 'lsusb'\n"
|
||||
"and make sure you have an up-to-date version of NUT. If this does not help,\n"
|
||||
"try running the driver with at least 'subdriver', 'vendorid' and 'productid'\n"
|
||||
"options specified. Please refer to the man page for details about these options\n"
|
||||
"(man 8 blazer).\n");
|
||||
}
|
||||
|
||||
if (!subdriver_command) {
|
||||
fatalx(EXIT_FAILURE, "No subdriver selected");
|
||||
}
|
||||
|
||||
/* create a new matcher for later reopening */
|
||||
ret = USBNewExactMatcher(&reopen_matcher, &usbdevice);
|
||||
if (ret) {
|
||||
fatal_with_errno(EXIT_FAILURE, "USBNewExactMatcher");
|
||||
}
|
||||
|
||||
/* link the matchers */
|
||||
reopen_matcher->next = regex_matcher;
|
||||
|
||||
dstate_setinfo("ups.vendorid", "%04x", usbdevice.VendorID);
|
||||
dstate_setinfo("ups.productid", "%04x", usbdevice.ProductID);
|
||||
#endif
|
||||
blazer_initups();
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_initinfo(void)
|
||||
{
|
||||
blazer_initinfo();
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
#ifndef TESTING
|
||||
usb->close(udev);
|
||||
USBFreeExactMatcher(reopen_matcher);
|
||||
USBFreeRegexMatcher(regex_matcher);
|
||||
free(usbdevice.Vendor);
|
||||
free(usbdevice.Product);
|
||||
free(usbdevice.Serial);
|
||||
free(usbdevice.Bus);
|
||||
#endif
|
||||
}
|
||||
418
drivers/clone-outlet.c
Normal file
418
drivers/clone-outlet.c
Normal file
|
|
@ -0,0 +1,418 @@
|
|||
/*
|
||||
* clone-outlet.c: clone outlet UPS driver
|
||||
*
|
||||
* Copyright (C) 2009 - Arjen de Korte <adkorte-guest@alioth.debian.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "parseconf.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#define DRIVER_NAME "clone outlet UPS Driver"
|
||||
#define DRIVER_VERSION "0.01"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Arjen de Korte <adkorte-guest@alioth.debian.org>",
|
||||
DRV_EXPERIMENTAL,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static struct {
|
||||
struct {
|
||||
char *shutdown;
|
||||
} delay;
|
||||
struct {
|
||||
char *shutdown;
|
||||
} timer;
|
||||
char *status;
|
||||
} prefix = { { NULL }, { NULL }, NULL };
|
||||
|
||||
static struct {
|
||||
struct {
|
||||
long shutdown;
|
||||
} delay;
|
||||
struct {
|
||||
long shutdown;
|
||||
} timer;
|
||||
int status;
|
||||
} outlet = { { -1 }, { -1 }, 1 };
|
||||
|
||||
static struct {
|
||||
char status[LARGEBUF];
|
||||
} ups = { "WAIT" };
|
||||
|
||||
static int dumpdone = 0;
|
||||
|
||||
static PCONF_CTX_t sock_ctx;
|
||||
static time_t last_heard = 0, last_ping = 0, last_connfail = 0;
|
||||
|
||||
static int parse_args(int numargs, char **arg)
|
||||
{
|
||||
if (numargs < 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!strcasecmp(arg[0], "PONG")) {
|
||||
upsdebugx(3, "Got PONG from UPS");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcasecmp(arg[0], "DUMPDONE")) {
|
||||
upsdebugx(3, "UPS: dump is done");
|
||||
dumpdone = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcasecmp(arg[0], "DATASTALE")) {
|
||||
dstate_datastale();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcasecmp(arg[0], "DATAOK")) {
|
||||
dstate_dataok();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (numargs < 2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* DELINFO <var> */
|
||||
if (!strcasecmp(arg[0], "DELINFO")) {
|
||||
dstate_delinfo(arg[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (numargs < 3) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* SETINFO <varname> <value> */
|
||||
if (!strcasecmp(arg[0], "SETINFO")) {
|
||||
|
||||
if (!strncasecmp(arg[1], "driver.", 7)) {
|
||||
/* don't pass on upstream driver settings */
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcasecmp(arg[1], prefix.delay.shutdown)) {
|
||||
outlet.delay.shutdown = strtol(arg[2], NULL, 10);
|
||||
}
|
||||
|
||||
if (!strcasecmp(arg[1], prefix.timer.shutdown)) {
|
||||
outlet.timer.shutdown = strtol(arg[2], NULL, 10);
|
||||
}
|
||||
|
||||
if (!strcasecmp(arg[1], prefix.status)) {
|
||||
outlet.status = strcasecmp(arg[2], "off");
|
||||
}
|
||||
|
||||
if (!strcasecmp(arg[1], "ups.status")) {
|
||||
snprintf(ups.status, sizeof(ups.status), "%s", arg[2]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
dstate_setinfo(arg[1], "%s", arg[2]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int sstate_connect(void)
|
||||
{
|
||||
int ret, fd;
|
||||
const char *dumpcmd = "DUMPALL\n";
|
||||
struct sockaddr_un sa;
|
||||
|
||||
memset(&sa, '\0', sizeof(sa));
|
||||
sa.sun_family = AF_UNIX;
|
||||
snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s", dflt_statepath(), device_path);
|
||||
|
||||
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
|
||||
if (fd < 0) {
|
||||
upslog_with_errno(LOG_ERR, "Can't create socket for UPS [%s]", device_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = connect(fd, (struct sockaddr *) &sa, sizeof(sa));
|
||||
|
||||
if (ret < 0) {
|
||||
time_t now;
|
||||
|
||||
close(fd);
|
||||
|
||||
/* rate-limit complaints - don't spam the syslog */
|
||||
time(&now);
|
||||
|
||||
if (difftime(now, last_connfail) < 60) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
last_connfail = now;
|
||||
|
||||
upslog_with_errno(LOG_ERR, "Can't connect to UPS [%s]", device_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = fcntl(fd, F_GETFL, 0);
|
||||
|
||||
if (ret < 0) {
|
||||
upslog_with_errno(LOG_ERR, "fcntl get on UPS [%s] failed", device_path);
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = fcntl(fd, F_SETFL, ret | O_NDELAY);
|
||||
|
||||
if (ret < 0) {
|
||||
upslog_with_errno(LOG_ERR, "fcntl set O_NDELAY on UPS [%s] failed", device_path);
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* get a dump started so we have a fresh set of data */
|
||||
ret = write(fd, dumpcmd, strlen(dumpcmd));
|
||||
|
||||
if (ret != (int)strlen(dumpcmd)) {
|
||||
upslog_with_errno(LOG_ERR, "Initial write to UPS [%s] failed", device_path);
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pconf_init(&sock_ctx, NULL);
|
||||
|
||||
time(&last_heard);
|
||||
|
||||
dumpdone = 0;
|
||||
|
||||
/* set ups.status to "WAIT" while waiting for the driver response to dumpcmd */
|
||||
dstate_setinfo("ups.status", "WAIT");
|
||||
|
||||
upslogx(LOG_INFO, "Connected to UPS [%s]", device_path);
|
||||
return fd;
|
||||
}
|
||||
|
||||
|
||||
static void sstate_disconnect(void)
|
||||
{
|
||||
if (upsfd < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
pconf_finish(&sock_ctx);
|
||||
|
||||
close(upsfd);
|
||||
upsfd = -1;
|
||||
}
|
||||
|
||||
|
||||
static int sstate_sendline(const char *buf)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (upsfd < 0) {
|
||||
return -1; /* failed */
|
||||
}
|
||||
|
||||
ret = write(upsfd, buf, strlen(buf));
|
||||
|
||||
if (ret == (int)strlen(buf)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
upslog_with_errno(LOG_NOTICE, "Send to UPS [%s] failed", device_path);
|
||||
return -1; /* failed */
|
||||
}
|
||||
|
||||
|
||||
static int sstate_readline(void)
|
||||
{
|
||||
int i, ret;
|
||||
char buf[SMALLBUF];
|
||||
|
||||
if (upsfd < 0) {
|
||||
return -1; /* failed */
|
||||
}
|
||||
|
||||
ret = read(upsfd, buf, sizeof(buf));
|
||||
|
||||
if (ret < 0) {
|
||||
switch(errno)
|
||||
{
|
||||
case EINTR:
|
||||
case EAGAIN:
|
||||
return 0;
|
||||
|
||||
default:
|
||||
upslog_with_errno(LOG_WARNING, "Read from UPS [%s] failed", device_path);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < ret; i++) {
|
||||
|
||||
switch (pconf_char(&sock_ctx, buf[i]))
|
||||
{
|
||||
case 1:
|
||||
if (parse_args(sock_ctx.numargs, sock_ctx.arglist)) {
|
||||
time(&last_heard);
|
||||
}
|
||||
continue;
|
||||
|
||||
case 0:
|
||||
continue; /* haven't gotten a line yet */
|
||||
|
||||
default:
|
||||
/* parse error */
|
||||
upslogx(LOG_NOTICE, "Parse error on sock: %s", sock_ctx.errmsg);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int sstate_dead(int maxage)
|
||||
{
|
||||
time_t now;
|
||||
double elapsed;
|
||||
|
||||
/* an unconnected ups is always dead */
|
||||
if (upsfd < 0) {
|
||||
upsdebugx(3, "sstate_dead: connection to driver socket for UPS [%s] lost", device_path);
|
||||
return -1; /* dead */
|
||||
}
|
||||
|
||||
time(&now);
|
||||
|
||||
/* ignore DATAOK/DATASTALE unless the dump is done */
|
||||
if (dumpdone && dstate_is_stale()) {
|
||||
upsdebugx(3, "sstate_dead: driver for UPS [%s] says data is stale", device_path);
|
||||
return -1; /* dead */
|
||||
}
|
||||
|
||||
elapsed = difftime(now, last_heard);
|
||||
|
||||
/* somewhere beyond a third of the maximum time - prod it to make it talk */
|
||||
if ((elapsed > (maxage / 3)) && (difftime(now, last_ping) > (maxage / 3))) {
|
||||
upsdebugx(3, "Send PING to UPS");
|
||||
sstate_sendline("PING\n");
|
||||
last_ping = now;
|
||||
}
|
||||
|
||||
if (elapsed > maxage) {
|
||||
upsdebugx(3, "sstate_dead: didn't hear from driver for UPS [%s] for %g seconds (max %d)",
|
||||
device_path, elapsed, maxage);
|
||||
return -1; /* dead */
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_initinfo(void)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
if (sstate_dead(15)) {
|
||||
sstate_disconnect();
|
||||
extrafd = upsfd = sstate_connect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (sstate_readline()) {
|
||||
sstate_disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (outlet.status == 0) {
|
||||
upsdebugx(2, "OFF flag set (%s: switched off)", prefix.status);
|
||||
dstate_setinfo("ups.status", "%s OFF", ups.status);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((outlet.timer.shutdown > -1) && (outlet.timer.shutdown <= outlet.delay.shutdown)) {
|
||||
upsdebugx(2, "FSD flag set (%s: -1 < [%ld] <= %ld)", prefix.timer.shutdown, outlet.timer.shutdown, outlet.delay.shutdown);
|
||||
dstate_setinfo("ups.status", "FSD %s", ups.status);
|
||||
return;
|
||||
}
|
||||
|
||||
upsdebugx(3, "%s: power state not critical", getval("prefix"));
|
||||
dstate_setinfo("ups.status", "%s", ups.status);
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
addvar(VAR_VALUE, "prefix", "Outlet prefix (mandatory)");
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
char buf[SMALLBUF];
|
||||
const char *val;
|
||||
|
||||
val = getval("prefix");
|
||||
if (!val) {
|
||||
fatalx(EXIT_FAILURE, "Outlet prefix is mandatory for this driver!");
|
||||
}
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s.delay.shutdown", val);
|
||||
prefix.delay.shutdown = xstrdup(buf);
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s.timer.shutdown", val);
|
||||
prefix.timer.shutdown = xstrdup(buf);
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s.status", val);
|
||||
prefix.status = xstrdup(buf);
|
||||
|
||||
extrafd = upsfd = sstate_connect();
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
free(prefix.delay.shutdown);
|
||||
free(prefix.timer.shutdown);
|
||||
free(prefix.status);
|
||||
|
||||
sstate_disconnect();
|
||||
}
|
||||
557
drivers/clone.c
Normal file
557
drivers/clone.c
Normal file
|
|
@ -0,0 +1,557 @@
|
|||
/*
|
||||
* clone.c: UPS driver clone
|
||||
*
|
||||
* Copyright (C) 2009 - Arjen de Korte <adkorte-guest@alioth.debian.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "parseconf.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#define DRIVER_NAME "Clone UPS driver"
|
||||
#define DRIVER_VERSION "0.02"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Arjen de Korte <adkorte-guest@alioth.debian.org>",
|
||||
DRV_EXPERIMENTAL,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static struct {
|
||||
struct {
|
||||
int start;
|
||||
int shutdown;
|
||||
} timer;
|
||||
char status[ST_MAX_VALUE_LEN];
|
||||
} ups = { { -1, -1 }, "WAIT" };
|
||||
|
||||
static struct {
|
||||
struct {
|
||||
double act;
|
||||
double low;
|
||||
} charge;
|
||||
struct {
|
||||
double act;
|
||||
double low;
|
||||
} runtime;
|
||||
} battery = { { 0, 0 }, { 0, 0 } };
|
||||
|
||||
static int dumpdone = 0, online = 1, outlet = 1;
|
||||
static int offdelay = 120, ondelay = 30;
|
||||
|
||||
static PCONF_CTX_t sock_ctx;
|
||||
static time_t last_poll = 0, last_heard = 0,
|
||||
last_ping = 0, last_connfail = 0;
|
||||
|
||||
static int instcmd(const char *cmdname, const char *extra);
|
||||
|
||||
|
||||
static int parse_args(int numargs, char **arg)
|
||||
{
|
||||
if (numargs < 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!strcasecmp(arg[0], "PONG")) {
|
||||
upsdebugx(3, "Got PONG from UPS");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcasecmp(arg[0], "DUMPDONE")) {
|
||||
upsdebugx(3, "UPS: dump is done");
|
||||
dumpdone = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcasecmp(arg[0], "DATASTALE")) {
|
||||
dstate_datastale();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcasecmp(arg[0], "DATAOK")) {
|
||||
dstate_dataok();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (numargs < 2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* DELINFO <var> */
|
||||
if (!strcasecmp(arg[0], "DELINFO")) {
|
||||
dstate_delinfo(arg[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (numargs < 3) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* SETINFO <varname> <value> */
|
||||
if (!strcasecmp(arg[0], "SETINFO")) {
|
||||
|
||||
if (!strncasecmp(arg[1], "driver.", 7) ||
|
||||
!strcasecmp(arg[1], "battery.charge.low") ||
|
||||
!strcasecmp(arg[1], "battery.runtime.low") ||
|
||||
!strncasecmp(arg[1], "ups.delay.", 10) ||
|
||||
!strncasecmp(arg[1], "ups.timer.", 10)) {
|
||||
/* don't pass on upstream driver settings */
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcasecmp(arg[1], "ups.status")) {
|
||||
snprintf(ups.status, sizeof(ups.status), "%s", arg[2]);
|
||||
|
||||
online = strstr(ups.status, "OL") ? 1 : 0;
|
||||
|
||||
if (ups.timer.shutdown > 0) {
|
||||
dstate_setinfo("ups.status", "FSD %s", ups.status);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!strcasecmp(arg[1], "battery.charge")) {
|
||||
battery.charge.act = strtod(arg[2], NULL);
|
||||
|
||||
dstate_setinfo("battery.charge.low", "%g", battery.charge.low);
|
||||
dstate_setflags("battery.charge.low", ST_FLAG_RW | ST_FLAG_STRING);
|
||||
dstate_setaux("battery.charge.low", 3);
|
||||
}
|
||||
|
||||
if (!strcasecmp(arg[1], "battery.runtime")) {
|
||||
battery.runtime.act = strtod(arg[2], NULL);
|
||||
|
||||
dstate_setinfo("battery.runtime.low", "%g", battery.runtime.low);
|
||||
dstate_setflags("battery.runtime.low", ST_FLAG_RW | ST_FLAG_STRING);
|
||||
dstate_setaux("battery.runtime.low", 4);
|
||||
}
|
||||
|
||||
dstate_setinfo(arg[1], "%s", arg[2]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int sstate_connect(void)
|
||||
{
|
||||
int ret, fd;
|
||||
const char *dumpcmd = "DUMPALL\n";
|
||||
struct sockaddr_un sa;
|
||||
|
||||
memset(&sa, '\0', sizeof(sa));
|
||||
sa.sun_family = AF_UNIX;
|
||||
snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s", dflt_statepath(), device_path);
|
||||
|
||||
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
|
||||
if (fd < 0) {
|
||||
upslog_with_errno(LOG_ERR, "Can't create socket for UPS [%s]", device_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = connect(fd, (struct sockaddr *) &sa, sizeof(sa));
|
||||
|
||||
if (ret < 0) {
|
||||
time_t now;
|
||||
|
||||
close(fd);
|
||||
|
||||
/* rate-limit complaints - don't spam the syslog */
|
||||
time(&now);
|
||||
|
||||
if (difftime(now, last_connfail) < 60) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
last_connfail = now;
|
||||
|
||||
upslog_with_errno(LOG_ERR, "Can't connect to UPS [%s]", device_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = fcntl(fd, F_GETFL, 0);
|
||||
|
||||
if (ret < 0) {
|
||||
upslog_with_errno(LOG_ERR, "fcntl get on UPS [%s] failed", device_path);
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = fcntl(fd, F_SETFL, ret | O_NDELAY);
|
||||
|
||||
if (ret < 0) {
|
||||
upslog_with_errno(LOG_ERR, "fcntl set O_NDELAY on UPS [%s] failed", device_path);
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* get a dump started so we have a fresh set of data */
|
||||
ret = write(fd, dumpcmd, strlen(dumpcmd));
|
||||
|
||||
if (ret != (int)strlen(dumpcmd)) {
|
||||
upslog_with_errno(LOG_ERR, "Initial write to UPS [%s] failed", device_path);
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pconf_init(&sock_ctx, NULL);
|
||||
|
||||
time(&last_heard);
|
||||
|
||||
dumpdone = 0;
|
||||
|
||||
/* set ups.status to "WAIT" while waiting for the driver response to dumpcmd */
|
||||
dstate_setinfo("ups.status", "WAIT");
|
||||
|
||||
upslogx(LOG_INFO, "Connected to UPS [%s]", device_path);
|
||||
return fd;
|
||||
}
|
||||
|
||||
|
||||
static void sstate_disconnect(void)
|
||||
{
|
||||
if (upsfd < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
pconf_finish(&sock_ctx);
|
||||
|
||||
close(upsfd);
|
||||
upsfd = -1;
|
||||
}
|
||||
|
||||
|
||||
static int sstate_sendline(const char *buf)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (upsfd < 0) {
|
||||
return -1; /* failed */
|
||||
}
|
||||
|
||||
ret = write(upsfd, buf, strlen(buf));
|
||||
|
||||
if (ret == (int)strlen(buf)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
upslog_with_errno(LOG_NOTICE, "Send to UPS [%s] failed", device_path);
|
||||
return -1; /* failed */
|
||||
}
|
||||
|
||||
|
||||
static int sstate_readline(void)
|
||||
{
|
||||
int i, ret;
|
||||
char buf[SMALLBUF];
|
||||
|
||||
if (upsfd < 0) {
|
||||
return -1; /* failed */
|
||||
}
|
||||
|
||||
ret = read(upsfd, buf, sizeof(buf));
|
||||
|
||||
if (ret < 0) {
|
||||
switch(errno)
|
||||
{
|
||||
case EINTR:
|
||||
case EAGAIN:
|
||||
return 0;
|
||||
|
||||
default:
|
||||
upslog_with_errno(LOG_WARNING, "Read from UPS [%s] failed", device_path);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < ret; i++) {
|
||||
|
||||
switch (pconf_char(&sock_ctx, buf[i]))
|
||||
{
|
||||
case 1:
|
||||
if (parse_args(sock_ctx.numargs, sock_ctx.arglist)) {
|
||||
time(&last_heard);
|
||||
}
|
||||
continue;
|
||||
|
||||
case 0:
|
||||
continue; /* haven't gotten a line yet */
|
||||
|
||||
default:
|
||||
/* parse error */
|
||||
upslogx(LOG_NOTICE, "Parse error on sock: %s", sock_ctx.errmsg);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int sstate_dead(int maxage)
|
||||
{
|
||||
time_t now;
|
||||
double elapsed;
|
||||
|
||||
/* an unconnected ups is always dead */
|
||||
if (upsfd < 0) {
|
||||
upsdebugx(3, "sstate_dead: connection to driver socket for UPS [%s] lost", device_path);
|
||||
return -1; /* dead */
|
||||
}
|
||||
|
||||
time(&now);
|
||||
|
||||
/* ignore DATAOK/DATASTALE unless the dump is done */
|
||||
if (dumpdone && dstate_is_stale()) {
|
||||
upsdebugx(3, "sstate_dead: driver for UPS [%s] says data is stale", device_path);
|
||||
return -1; /* dead */
|
||||
}
|
||||
|
||||
elapsed = difftime(now, last_heard);
|
||||
|
||||
/* somewhere beyond a third of the maximum time - prod it to make it talk */
|
||||
if ((elapsed > (maxage / 3)) && (difftime(now, last_ping) > (maxage / 3))) {
|
||||
upsdebugx(3, "Send PING to UPS");
|
||||
sstate_sendline("PING\n");
|
||||
last_ping = now;
|
||||
}
|
||||
|
||||
if (elapsed > maxage) {
|
||||
upsdebugx(3, "sstate_dead: didn't hear from driver for UPS [%s] for %g seconds (max %d)",
|
||||
device_path, elapsed, maxage);
|
||||
return -1; /* dead */
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int instcmd(const char *cmdname, const char *extra)
|
||||
{
|
||||
const char *val;
|
||||
|
||||
val = dstate_getinfo(getval("load.status"));
|
||||
if (val) {
|
||||
if (!strcasecmp(val, "off") || !strcasecmp(val, "no")) {
|
||||
outlet = 0;
|
||||
}
|
||||
|
||||
if (!strcasecmp(val, "on") || !strcasecmp(val, "yes")) {
|
||||
outlet = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!strcasecmp(cmdname, "shutdown.return")) {
|
||||
if (outlet && (ups.timer.shutdown < 0)) {
|
||||
ups.timer.shutdown = offdelay;
|
||||
dstate_setinfo("ups.status", "FSD %s", ups.status);
|
||||
}
|
||||
ups.timer.start = ondelay;
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
if (!strcasecmp(cmdname, "shutdown.stayoff")) {
|
||||
if (outlet && (ups.timer.shutdown < 0)) {
|
||||
ups.timer.shutdown = offdelay;
|
||||
dstate_setinfo("ups.status", "FSD %s", ups.status);
|
||||
}
|
||||
ups.timer.start = -1;
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
}
|
||||
|
||||
|
||||
static int setvar(const char *varname, const char *val)
|
||||
{
|
||||
if (!strcasecmp(varname, "battery.charge.low")) {
|
||||
battery.charge.low = strtod(val, NULL);
|
||||
dstate_setinfo("battery.charge.low", "%g", battery.charge.low);
|
||||
return STAT_SET_HANDLED;
|
||||
}
|
||||
|
||||
if (!strcasecmp(varname, "battery.runtime.low")) {
|
||||
battery.runtime.low = strtod(val, NULL);
|
||||
dstate_setinfo("battery.runtime.low", "%g", battery.runtime.low);
|
||||
return STAT_SET_HANDLED;
|
||||
}
|
||||
|
||||
upslogx(LOG_NOTICE, "setvar: unknown variable [%s]", varname);
|
||||
return STAT_SET_UNKNOWN;
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_initinfo(void)
|
||||
{
|
||||
const char *val;
|
||||
|
||||
time(&last_poll);
|
||||
|
||||
dstate_addcmd("shutdown.return");
|
||||
dstate_addcmd("shutdown.stayoff");
|
||||
|
||||
val = getval("offdelay");
|
||||
if (val) {
|
||||
offdelay = strtol(val, NULL, 10);
|
||||
}
|
||||
|
||||
val = getval("ondelay");
|
||||
if (val) {
|
||||
ondelay = strtol(val, NULL, 10);
|
||||
}
|
||||
|
||||
val = getval("mincharge");
|
||||
if (val) {
|
||||
battery.charge.low = strtod(val, NULL);
|
||||
}
|
||||
|
||||
val = getval("minruntime");
|
||||
if (val) {
|
||||
battery.runtime.low = strtod(val, NULL);
|
||||
}
|
||||
|
||||
dstate_setinfo("ups.delay.shutdown", "%d", offdelay);
|
||||
dstate_setinfo("ups.delay.start", "%d", ondelay);
|
||||
|
||||
dstate_setinfo("ups.timer.shutdown", "%d", ups.timer.shutdown);
|
||||
dstate_setinfo("ups.timer.start", "%d", ups.timer.start);
|
||||
|
||||
upsh.instcmd = instcmd;
|
||||
upsh.setvar = setvar;
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
time_t now = time(NULL);
|
||||
|
||||
if (sstate_dead(15)) {
|
||||
sstate_disconnect();
|
||||
extrafd = upsfd = sstate_connect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (sstate_readline()) {
|
||||
sstate_disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ups.timer.shutdown >= 0) {
|
||||
|
||||
ups.timer.shutdown -= difftime(now, last_poll);
|
||||
|
||||
if (ups.timer.shutdown < 0) {
|
||||
const char *val;
|
||||
|
||||
ups.timer.shutdown = -1;
|
||||
outlet = 0;
|
||||
|
||||
val = getval("load.off");
|
||||
if (val) {
|
||||
char buf[SMALLBUF];
|
||||
snprintf(buf, sizeof(buf), "INSTCMD %s\n", val);
|
||||
sstate_sendline(buf);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (ups.timer.start >= 0) {
|
||||
|
||||
if (online) {
|
||||
ups.timer.start -= difftime(now, last_poll);
|
||||
} else {
|
||||
ups.timer.start = ondelay;
|
||||
}
|
||||
|
||||
if (ups.timer.start < 0) {
|
||||
const char *val;
|
||||
|
||||
ups.timer.start = -1;
|
||||
outlet = 1;
|
||||
|
||||
val = getval("load.on");
|
||||
if (val) {
|
||||
char buf[SMALLBUF];
|
||||
snprintf(buf, sizeof(buf), "INSTCMD %s\n", val);
|
||||
sstate_sendline(buf);
|
||||
}
|
||||
|
||||
dstate_setinfo("ups.status", "%s", ups.status);
|
||||
}
|
||||
|
||||
} else if (!online && outlet) {
|
||||
|
||||
if (battery.charge.act < battery.charge.low) {
|
||||
upslogx(LOG_INFO, "Battery charge low");
|
||||
instcmd("shutdown.return", NULL);
|
||||
} else if (battery.runtime.act < battery.runtime.low) {
|
||||
upslogx(LOG_INFO, "Battery runtime low");
|
||||
instcmd("shutdown.return", NULL);
|
||||
}
|
||||
}
|
||||
|
||||
dstate_setinfo("ups.timer.shutdown", "%d", ups.timer.shutdown);
|
||||
dstate_setinfo("ups.timer.start", "%d", ups.timer.start);
|
||||
|
||||
last_poll = now;
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
fatalx(EXIT_FAILURE, "shutdown not supported");
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
addvar(VAR_VALUE, "offdelay", "Delay before outlet shutdown (seconds)");
|
||||
addvar(VAR_VALUE, "ondelay", "Delay before outlet startup (seconds)");
|
||||
|
||||
addvar(VAR_VALUE, "mincharge", "Remaining battery level when UPS switches to LB (percent)");
|
||||
addvar(VAR_VALUE, "minruntime", "Remaining battery runtime when UPS switches to LB (seconds)");
|
||||
|
||||
addvar(VAR_VALUE, "load.off", "Command to switch off outlet");
|
||||
addvar(VAR_VALUE, "load.on", "Command to switch on outlet");
|
||||
addvar(VAR_VALUE, "load.status", "Variable that indicates outlet is on/off");
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
extrafd = upsfd = sstate_connect();
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
sstate_disconnect();
|
||||
}
|
||||
208
drivers/compaq-mib.c
Normal file
208
drivers/compaq-mib.c
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
/* compaq-mib.c - data to monitor SNMP UPS with NUT
|
||||
*
|
||||
* Copyright (C) 2002-2006
|
||||
* Arnaud Quette <arnaud.quette@free.fr>
|
||||
* Niels Baggesen <niels@baggesen.net>
|
||||
* Philip Ward <p.g.ward@stir.ac.uk>
|
||||
*
|
||||
* Sponsored by MGE UPS SYSTEMS <http://www.mgeups.com>
|
||||
*
|
||||
* This version has been tested using an HP R5500XR UPS with AF401A
|
||||
* management card and a single phase input.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "compaq-mib.h"
|
||||
|
||||
#define CPQPOWER_MIB_VERSION "1.0"
|
||||
|
||||
/* SNMP OIDs set */
|
||||
#define CPQPOWER_OID_UPS_MIB "1.3.6.1.4.1.232.165.3"
|
||||
|
||||
#define CPQPOWER_OID_MFR_NAME CPQPOWER_OID_UPS_MIB ".1.1.0" /* UPS-MIB::upsIdentManufacturer */
|
||||
#define CPQPOWER_OID_MODEL_NAME CPQPOWER_OID_UPS_MIB ".1.2.0" /* UPS-MIB::upsIdentModel */
|
||||
#define CPQPOWER_OID_FIRMREV CPQPOWER_OID_UPS_MIB ".1.3.0" /* UPS-MIB::upsIdentUPSSoftwareVersion */
|
||||
#define CPQPOWER_OID_OEMCODE CPQPOWER_OID_UPS_MIB ".1.4.0" /* UPS-MIB::upsIdentAgentSoftwareVersion */
|
||||
|
||||
#define CPQPOWER_OID_BATT_RUNTIME CPQPOWER_OID_UPS_MIB ".2.1.0" /* UPS-MIB::upsEstimatedMinutesRemaining */
|
||||
#define CPQPOWER_OID_BATT_VOLTAGE CPQPOWER_OID_UPS_MIB ".2.2.0" /* UPS-MIB::upsBatteryVoltage */
|
||||
#define CPQPOWER_OID_BATT_CURRENT CPQPOWER_OID_UPS_MIB ".2.3.0" /* UPS-MIB::upsBatteryCurrent */
|
||||
#define CPQPOWER_OID_BATT_CHARGE CPQPOWER_OID_UPS_MIB ".2.4.0" /* UPS-MIB::upsBattCapacity */
|
||||
#define CPQPOWER_OID_BATT_STATUS CPQPOWER_OID_UPS_MIB ".2.5.0" /* UPS-MIB::upsBatteryAbmStatus */
|
||||
|
||||
#define CPQPOWER_OID_IN_FREQ CPQPOWER_OID_UPS_MIB ".3.1.0" /* UPS-MIB::upsInputFrequency */
|
||||
#define CPQPOWER_OID_IN_LINEBADS CPQPOWER_OID_UPS_MIB ".3.2.0" /* UPS-MIB::upsInputLineBads */
|
||||
#define CPQPOWER_OID_IN_LINES CPQPOWER_OID_UPS_MIB ".3.3.0" /* UPS-MIB::upsInputNumPhases */
|
||||
|
||||
#define CPQPOWER_OID_IN_PHASE CPQPOWER_OID_UPS_MIB ".3.4.1.1" /* UPS-MIB::upsInputPhase */
|
||||
#define CPQPOWER_OID_IN_VOLTAGE CPQPOWER_OID_UPS_MIB ".3.4.1.2" /* UPS-MIB::upsInputVoltage */
|
||||
#define CPQPOWER_OID_IN_CURRENT CPQPOWER_OID_UPS_MIB ".3.4.1.3" /* UPS-MIB::upsInputCurrent */
|
||||
#define CPQPOWER_OID_IN_POWER CPQPOWER_OID_UPS_MIB ".3.4.1.4" /* UPS-MIB::upsInputWatts */
|
||||
|
||||
#define CPQPOWER_OID_LOAD_LEVEL CPQPOWER_OID_UPS_MIB ".4.1.0" /* UPS-MIB::upsOutputLoad */
|
||||
#define CPQPOWER_OID_OUT_FREQUENCY CPQPOWER_OID_UPS_MIB ".4.2.0" /* UPS-MIB::upsOutputFrequency */
|
||||
#define CPQPOWER_OID_OUT_LINES CPQPOWER_OID_UPS_MIB ".4.3.0" /* UPS-MIB::upsOutputNumPhases */
|
||||
|
||||
#define CPQPOWER_OID_OUT_PHASE CPQPOWER_OID_UPS_MIB ".4.4.1.1" /* UPS-MIB::upsOutputPhase */
|
||||
#define CPQPOWER_OID_OUT_VOLTAGE CPQPOWER_OID_UPS_MIB ".4.4.1.2" /* UPS-MIB::upsOutputVoltage */
|
||||
#define CPQPOWER_OID_OUT_CURRENT CPQPOWER_OID_UPS_MIB ".4.4.1.3" /* UPS-MIB::upsOutputCurrent */
|
||||
#define CPQPOWER_OID_OUT_POWER CPQPOWER_OID_UPS_MIB ".4.4.1.4" /* UPS-MIB::upsOutputWatts */
|
||||
|
||||
#define CPQPOWER_OID_POWER_STATUS CPQPOWER_OID_UPS_MIB ".4.5.0" /* UPS-MIB::upsOutputSource */
|
||||
|
||||
#define CPQPOWER_OID_AMBIENT_TEMP CPQPOWER_OID_UPS_MIB ".6.1.0" /* UPS-MIB::upsEnvAmbientTemp */
|
||||
|
||||
#define CPQPOWER_OID_UPS_TEST_BATT CPQPOWER_OID_UPS_MIB ".7.1.0" /* UPS-MIB::upsTestBattery */
|
||||
#define CPQPOWER_OID_UPS_TEST_RES CPQPOWER_OID_UPS_MIB ".7.2.0" /* UPS-MIB::upsTestBatteryStatus */
|
||||
#define CPQPOWER_OID_ALARM_OB CPQPOWER_OID_UPS_MIB ".7.3.0" /* UPS-MIB::upsOnBattery */
|
||||
#define CPQPOWER_OID_ALARM_LB CPQPOWER_OID_UPS_MIB ".7.4.0" /* UPS-MIB::upsLowBattery */
|
||||
|
||||
static info_lkp_t cpqpower_alarm_ob[] = {
|
||||
{ 1, "OB" },
|
||||
{ 0, "NULL" }
|
||||
} ;
|
||||
|
||||
static info_lkp_t cpqpower_alarm_lb[] = {
|
||||
{ 1, "LB" },
|
||||
{ 0, "NULL" }
|
||||
} ;
|
||||
|
||||
/* Defines for CPQPOWER_OID_POWER_STATUS (1) */
|
||||
static info_lkp_t cpqpower_pwr_info[] = {
|
||||
{ 1, "" /* other */ },
|
||||
{ 2, "OFF" /* none */ },
|
||||
{ 3, "OL" /* normal */ },
|
||||
{ 4, "OL BYPASS" /* bypass */ },
|
||||
{ 5, "OB" /* battery */ },
|
||||
{ 6, "OL BOOST" /* booster */ },
|
||||
{ 7, "OL TRIM" /* reducer */ },
|
||||
{ 8, "OL" /* parallelCapacity */ },
|
||||
{ 9, "OL" /* parallelRedundant */ },
|
||||
{ 10, "OL" /* HighEfficiencyMode */ },
|
||||
{ 0, "NULL" }
|
||||
} ;
|
||||
|
||||
static info_lkp_t cpqpower_mode_info[] = {
|
||||
{ 1, "" },
|
||||
{ 2, "" },
|
||||
{ 3, "normal" },
|
||||
{ 4, "" },
|
||||
{ 5, "" },
|
||||
{ 6, "" },
|
||||
{ 7, "" },
|
||||
{ 8, "parallel capacity" },
|
||||
{ 9, "parallel redundancy" },
|
||||
{10, "high efficiency" },
|
||||
{ 0, "NULL" }
|
||||
};
|
||||
|
||||
static info_lkp_t cpqpower_battery_abm_status[] = {
|
||||
{ 1, "CHRG" },
|
||||
{ 2, "DISCHRG" },
|
||||
/* { 3, "Floating" }, */
|
||||
/* { 4, "Resting" }, */
|
||||
/* { 5, "Unknown" }, */
|
||||
{ 0, "NULL" }
|
||||
} ;
|
||||
|
||||
/* Defines for CPQPOWER_OID_UPS_TEST_RES */
|
||||
static info_lkp_t cpqpower_test_res_info[] = {
|
||||
{ 1, "Unknown" },
|
||||
{ 2, "Done and passed" },
|
||||
{ 3, "Done and error" },
|
||||
{ 4, "In progress" },
|
||||
{ 5, "Not supported" },
|
||||
{ 6, "Inhibited" },
|
||||
{ 7, "Scheduled" },
|
||||
{ 0, "NULL" }
|
||||
} ;
|
||||
|
||||
#define CPQPOWER_OID_SD_AFTER_DELAY CPQPOWER_OID_UPS_MIB ".8.1.0" /* UPS-MIB::upsShutdownAfterDelay */
|
||||
#define CPQPOWER_OFF_DO 0
|
||||
|
||||
/* Snmp2NUT lookup table */
|
||||
|
||||
static snmp_info_t cpqpower_mib[] = {
|
||||
/* UPS page */
|
||||
/* info_type, info_flags, info_len, OID, dfl, flags, oid2info, setvar */
|
||||
{ "ups.mfr", ST_FLAG_STRING, SU_INFOSIZE, CPQPOWER_OID_MFR_NAME, "HP/Compaq", SU_FLAG_STATIC, NULL },
|
||||
{ "ups.model", ST_FLAG_STRING, SU_INFOSIZE, CPQPOWER_OID_MODEL_NAME, "SNMP UPS", SU_FLAG_STATIC, NULL },
|
||||
{ "ups.model.aux", ST_FLAG_STRING, SU_INFOSIZE, CPQPOWER_OID_OEMCODE, "", SU_FLAG_STATIC, NULL },
|
||||
{ "ups.firmware", ST_FLAG_STRING, SU_INFOSIZE, CPQPOWER_OID_FIRMREV, "", SU_FLAG_STATIC, NULL },
|
||||
{ "ups.load", 0, 1.0, CPQPOWER_OID_LOAD_LEVEL, "", 0, NULL },
|
||||
{ "ups.realpower", 0, 1.0, CPQPOWER_OID_OUT_POWER, "", SU_OUTPUT_1, NULL },
|
||||
{ "ups.L1.realpower", 0, 0.1, CPQPOWER_OID_OUT_POWER ".1", "", SU_OUTPUT_3, NULL },
|
||||
{ "ups.L2.realpower", 0, 0.1, CPQPOWER_OID_OUT_POWER ".2", "", SU_OUTPUT_3, NULL },
|
||||
{ "ups.L3.realpower", 0, 0.1, CPQPOWER_OID_OUT_POWER ".3", "", SU_OUTPUT_3, NULL },
|
||||
{ "ups.status", ST_FLAG_STRING, SU_INFOSIZE, CPQPOWER_OID_POWER_STATUS, "OFF", SU_STATUS_PWR, cpqpower_pwr_info },
|
||||
{ "ups.status", ST_FLAG_STRING, SU_INFOSIZE, CPQPOWER_OID_BATT_STATUS, "", SU_STATUS_PWR, cpqpower_battery_abm_status },
|
||||
{ "ups.status", ST_FLAG_STRING, SU_INFOSIZE, CPQPOWER_OID_ALARM_OB, "", SU_STATUS_BATT, cpqpower_alarm_ob },
|
||||
{ "ups.status", ST_FLAG_STRING, SU_INFOSIZE, CPQPOWER_OID_ALARM_LB, "", SU_STATUS_BATT, cpqpower_alarm_lb },
|
||||
/* { "ups.status", ST_FLAG_STRING, SU_INFOSIZE, IETF_OID_BATT_STATUS, "", SU_STATUS_BATT, ietf_batt_info }, */
|
||||
{ "ups.type", ST_FLAG_STRING, SU_INFOSIZE, CPQPOWER_OID_POWER_STATUS, "", SU_STATUS_PWR, cpqpower_mode_info },
|
||||
{ "ups.test.result", ST_FLAG_STRING, SU_INFOSIZE, CPQPOWER_OID_UPS_TEST_RES, "", 0, cpqpower_test_res_info },
|
||||
|
||||
/* Ambient page */
|
||||
{ "ambient.temperature", 0, 1.0, CPQPOWER_OID_AMBIENT_TEMP, "", 0, NULL },
|
||||
|
||||
/* Battery page */
|
||||
{ "battery.charge", 0, 1.0, CPQPOWER_OID_BATT_CHARGE, "", 0, NULL },
|
||||
{ "battery.runtime", 0, 60.0, CPQPOWER_OID_BATT_RUNTIME, "", 0, NULL },
|
||||
{ "battery.voltage", 0, 0.1, CPQPOWER_OID_BATT_VOLTAGE, "", 0, NULL },
|
||||
{ "battery.current", 0, 0.1, CPQPOWER_OID_BATT_CURRENT, "", 0, NULL },
|
||||
|
||||
/* Input page */
|
||||
{ "input.phases", 0, 1.0, CPQPOWER_OID_IN_LINES, "", SU_FLAG_SETINT, NULL, &input_phases },
|
||||
/* { "input.phase", 0, 1.0, CPQPOWER_OID_IN_PHASE, "", SU_OUTPUT_1, NULL }, */
|
||||
{ "input.frequency", 0, 0.1, CPQPOWER_OID_IN_FREQ , "", 0, NULL },
|
||||
{ "input.voltage", 0, 1.0, CPQPOWER_OID_IN_VOLTAGE, "", SU_OUTPUT_1, NULL },
|
||||
{ "input.L1-N.voltage", 0, 1.0, CPQPOWER_OID_IN_VOLTAGE ".1", "", SU_INPUT_3, NULL },
|
||||
{ "input.L2-N.voltage", 0, 1.0, CPQPOWER_OID_IN_VOLTAGE ".2", "", SU_INPUT_3, NULL },
|
||||
{ "input.L3-N.voltage", 0, 1.0, CPQPOWER_OID_IN_VOLTAGE ".3", "", SU_INPUT_3, NULL },
|
||||
{ "input.current", 0, 0.1, CPQPOWER_OID_IN_CURRENT, "", SU_OUTPUT_1, NULL },
|
||||
{ "input.L1.current", 0, 0.1, CPQPOWER_OID_IN_CURRENT ".1", "", SU_INPUT_3, NULL },
|
||||
{ "input.L2.current", 0, 0.1, CPQPOWER_OID_IN_CURRENT ".2", "", SU_INPUT_3, NULL },
|
||||
{ "input.L3.current", 0, 0.1, CPQPOWER_OID_IN_CURRENT ".3", "", SU_INPUT_3, NULL },
|
||||
{ "input.realpower", 0, 0.1, CPQPOWER_OID_IN_POWER, "", SU_OUTPUT_1, NULL },
|
||||
{ "input.L1.realpower", 0, 0.1, CPQPOWER_OID_IN_POWER ".1", "", SU_INPUT_3, NULL },
|
||||
{ "input.L2.realpower", 0, 0.1, CPQPOWER_OID_IN_POWER ".2", "", SU_INPUT_3, NULL },
|
||||
{ "input.L3.realpower", 0, 0.1, CPQPOWER_OID_IN_POWER ".3", "", SU_INPUT_3, NULL },
|
||||
{ "input.quality", 0, 1.0, CPQPOWER_OID_IN_LINEBADS, "", 0, NULL },
|
||||
|
||||
/* Output page */
|
||||
{ "output.phases", 0, 1.0, CPQPOWER_OID_OUT_LINES, "", SU_FLAG_SETINT, NULL, &output_phases },
|
||||
/* { "output.phase", 0, 1.0, CPQPOWER_OID_OUT_PHASE, "", SU_OUTPUT_1, NULL }, */
|
||||
{ "output.frequency", 0, 0.1, CPQPOWER_OID_OUT_FREQUENCY, "", 0, NULL },
|
||||
{ "output.voltage", 0, 1.0, CPQPOWER_OID_OUT_VOLTAGE, "", SU_OUTPUT_1, NULL },
|
||||
{ "output.L1-N.voltage", 0, 1.0, CPQPOWER_OID_OUT_VOLTAGE ".1", "", SU_OUTPUT_3, NULL },
|
||||
{ "output.L2-N.voltage", 0, 1.0, CPQPOWER_OID_OUT_VOLTAGE ".2", "", SU_OUTPUT_3, NULL },
|
||||
{ "output.L3-N.voltage", 0, 1.0, CPQPOWER_OID_OUT_VOLTAGE ".3", "", SU_OUTPUT_3, NULL },
|
||||
{ "output.current", 0, 0.1, CPQPOWER_OID_OUT_CURRENT, "", SU_OUTPUT_1, NULL },
|
||||
{ "output.L1.current", 0, 0.1, CPQPOWER_OID_OUT_CURRENT ".1", "", SU_OUTPUT_3, NULL },
|
||||
{ "output.L2.current", 0, 0.1, CPQPOWER_OID_OUT_CURRENT ".2", "", SU_OUTPUT_3, NULL },
|
||||
{ "output.L3.current", 0, 0.1, CPQPOWER_OID_OUT_CURRENT ".3", "", SU_OUTPUT_3, NULL },
|
||||
|
||||
/* instant commands. */
|
||||
{ "load.off", 0, CPQPOWER_OFF_DO, CPQPOWER_OID_SD_AFTER_DELAY, "", SU_TYPE_CMD, NULL },
|
||||
/* { CMD_SHUTDOWN, 0, CPQPOWER_OFF_GRACEFUL, CPQPOWER_OID_OFF, "", 0, NULL }, */
|
||||
|
||||
/* end of structure. */
|
||||
{ NULL, 0, 0, NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
mib2nut_info_t compaq = { "cpqpower", CPQPOWER_MIB_VERSION, "", CPQPOWER_OID_MFR_NAME, cpqpower_mib };
|
||||
9
drivers/compaq-mib.h
Normal file
9
drivers/compaq-mib.h
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#ifndef COMPAQ_MIB_H
|
||||
#define COMPAQ_MIB_H
|
||||
|
||||
#include "main.h"
|
||||
#include "snmp-ups.h"
|
||||
|
||||
extern mib2nut_info_t compaq;
|
||||
|
||||
#endif /* COMPAQ_MIB_H */
|
||||
210
drivers/cps-hid.c
Normal file
210
drivers/cps-hid.c
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
/* cps-hid.c - subdriver to monitor CPS USB/HID devices with NUT
|
||||
*
|
||||
* Copyright (C)
|
||||
* 2003 - 2008 Arnaud Quette <arnaud.quette@free.fr>
|
||||
* 2005 - 2006 Peter Selinger <selinger@users.sourceforge.net>
|
||||
*
|
||||
* Note: this subdriver was initially generated as a "stub" by the
|
||||
* path-to-subdriver script. It must be customized.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "main.h" /* for getval() */
|
||||
#include "usbhid-ups.h"
|
||||
#include "cps-hid.h"
|
||||
#include "usb-common.h"
|
||||
|
||||
#define CPS_HID_VERSION "CyberPower HID 0.3"
|
||||
|
||||
/* Cyber Power Systems */
|
||||
#define CPS_VENDORID 0x0764
|
||||
|
||||
/*
|
||||
* For some devices, the reported battery voltage is off by factor
|
||||
* of 1.5 so we need to apply a scale factor to it to get the real
|
||||
* battery voltage. By default, the factor is 1 (no scaling).
|
||||
*/
|
||||
static double battery_scale = 1;
|
||||
|
||||
static void *cps_battery_scale(void)
|
||||
{
|
||||
battery_scale = 0.667;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* USB IDs device table */
|
||||
static usb_device_id_t cps_usb_device_table[] = {
|
||||
/* 900AVR/BC900D, CP1200AVR/BC1200D */
|
||||
{ USB_DEVICE(CPS_VENDORID, 0x0005), NULL },
|
||||
/* Dynex DX-800U? */
|
||||
{ USB_DEVICE(CPS_VENDORID, 0x0501), &cps_battery_scale },
|
||||
/* OR2200LCDRM2U */
|
||||
{ USB_DEVICE(CPS_VENDORID, 0x0601), NULL },
|
||||
|
||||
/* Terminating entry */
|
||||
{ -1, -1, NULL }
|
||||
};
|
||||
|
||||
/* returns statically allocated string - must not use it again before
|
||||
done with result! */
|
||||
static char *cps_battvolt_fun(double value)
|
||||
{
|
||||
static char buf[8];
|
||||
|
||||
snprintf(buf, sizeof(buf), "%.1f", battery_scale * value);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static info_lkp_t cps_battvolt[] = {
|
||||
{ 0, NULL, &cps_battvolt_fun }
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* Vendor-specific usage table */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
/* CPS usage table */
|
||||
static usage_lkp_t cps_usage_lkp[] = {
|
||||
{ NULL, 0x0 }
|
||||
};
|
||||
|
||||
static usage_tables_t cps_utab[] = {
|
||||
cps_usage_lkp,
|
||||
hid_usage_lkp,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* HID2NUT lookup table */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
static hid_info_t cps_hid2nut[] = {
|
||||
/* { "unmapped.ups.powersummary.rechargeable", 0, 0, "UPS.PowerSummary.Rechargeable", NULL, "%.0f", 0, NULL }, */
|
||||
/* { "unmapped.ups.powersummary.capacitymode", 0, 0, "UPS.PowerSummary.CapacityMode", NULL, "%.0f", 0, NULL }, */
|
||||
/* { "unmapped.ups.powersummary.designcapacity", 0, 0, "UPS.PowerSummary.DesignCapacity", NULL, "%.0f", 0, NULL }, */
|
||||
/* { "unmapped.ups.powersummary.capacitygranularity1", 0, 0, "UPS.PowerSummary.CapacityGranularity1", NULL, "%.0f", 0, NULL }, */
|
||||
/* { "unmapped.ups.powersummary.capacitygranularity2", 0, 0, "UPS.PowerSummary.CapacityGranularity2", NULL, "%.0f", 0, NULL }, */
|
||||
/* { "unmapped.ups.powersummary.fullchargecapacity", 0, 0, "UPS.PowerSummary.FullChargeCapacity", NULL, "%.0f", 0, NULL }, */
|
||||
|
||||
/* Battery page */
|
||||
{ "battery.type", 0, 0, "UPS.PowerSummary.iDeviceChemistry", NULL, "%s", 0, stringid_conversion },
|
||||
{ "battery.mfr.date", 0, 0, "UPS.PowerSummary.iOEMInformation", NULL, "%s", 0, stringid_conversion },
|
||||
{ "battery.charge.warning", 0, 0, "UPS.PowerSummary.WarningCapacityLimit", NULL, "%.0f", 0, NULL },
|
||||
{ "battery.charge.low", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.PowerSummary.RemainingCapacityLimit", NULL, "%.0f", HU_FLAG_SEMI_STATIC, NULL },
|
||||
{ "battery.charge", 0, 0, "UPS.PowerSummary.RemainingCapacity", NULL, "%.0f", 0, NULL },
|
||||
{ "battery.runtime", 0, 0, "UPS.PowerSummary.RunTimeToEmpty", NULL, "%.0f", 0, NULL },
|
||||
{ "battery.runtime.low", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.PowerSummary.RemainingTimeLimit", NULL, "%.0f", HU_FLAG_SEMI_STATIC, NULL },
|
||||
{ "battery.voltage.nominal", 0, 0, "UPS.PowerSummary.ConfigVoltage", NULL, "%.0f", 0, NULL },
|
||||
{ "battery.voltage", 0, 0, "UPS.PowerSummary.Voltage", NULL, "%s", 0, cps_battvolt },
|
||||
|
||||
/* UPS page */
|
||||
{ "ups.load", 0, 0, "UPS.Output.PercentLoad", NULL, "%.0f", 0, NULL },
|
||||
{ "ups.beeper.status", 0, 0, "UPS.PowerSummary.AudibleAlarmControl", NULL, "%s", 0, beeper_info },
|
||||
{ "ups.test.result", 0, 0, "UPS.Output.Test", NULL, "%s", 0, test_read_info },
|
||||
{ "ups.realpower.nominal", 0, 0, "UPS.Output.ConfigActivePower", NULL, "%.0f", 0, NULL },
|
||||
{ "ups.delay.start", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.Output.DelayBeforeStartup", NULL, DEFAULT_ONDELAY, HU_FLAG_ABSENT, NULL},
|
||||
{ "ups.delay.shutdown", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.Output.DelayBeforeShutdown", NULL, DEFAULT_OFFDELAY, HU_FLAG_ABSENT, NULL},
|
||||
{ "ups.timer.start", 0, 0, "UPS.Output.DelayBeforeStartup", NULL, "%.0f", HU_FLAG_QUICK_POLL, NULL},
|
||||
{ "ups.timer.shutdown", 0, 0, "UPS.Output.DelayBeforeShutdown", NULL, "%.0f", HU_FLAG_QUICK_POLL, NULL},
|
||||
{ "ups.timer.reboot", 0, 0, "UPS.Output.DelayBeforeReboot", NULL, "%.0f", HU_FLAG_QUICK_POLL, NULL},
|
||||
|
||||
/* Special case: ups.status & ups.alarm */
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.ACPresent", NULL, NULL, HU_FLAG_QUICK_POLL, online_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.Charging", NULL, NULL, HU_FLAG_QUICK_POLL, charging_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.Discharging", NULL, NULL, HU_FLAG_QUICK_POLL, discharging_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.BelowRemainingCapacityLimit", NULL, NULL, HU_FLAG_QUICK_POLL, lowbatt_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.FullyCharged", NULL, NULL, 0, fullycharged_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.RemainingTimeLimitExpired", NULL, NULL, 0, timelimitexpired_info },
|
||||
{ "BOOL", 0, 0, "UPS.Output.Boost", NULL, NULL, 0, boost_info },
|
||||
{ "BOOL", 0, 0, "UPS.Output.Overload", NULL, NULL, 0, overload_info },
|
||||
|
||||
/* Input page */
|
||||
{ "input.voltage.nominal", 0, 0, "UPS.Input.ConfigVoltage", NULL, "%.0f", 0, NULL },
|
||||
{ "input.voltage", 0, 0, "UPS.Input.Voltage", NULL, "%.1f", 0, NULL },
|
||||
{ "input.transfer.low", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.Input.LowVoltageTransfer", NULL, "%.0f", HU_FLAG_SEMI_STATIC, NULL },
|
||||
{ "input.transfer.high", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.Input.HighVoltageTransfer", NULL, "%.0f", HU_FLAG_SEMI_STATIC, NULL },
|
||||
|
||||
/* Output page */
|
||||
{ "output.voltage", 0, 0, "UPS.Output.Voltage", NULL, "%.1f", 0, NULL },
|
||||
{ "output.voltage.nominal", 0, 0, "UPS.Output.ConfigVoltage", NULL, "%.0f", 0, NULL },
|
||||
|
||||
/* instant commands. */
|
||||
{ "test.battery.start.quick", 0, 0, "UPS.Output.Test", NULL, "1", HU_TYPE_CMD, NULL },
|
||||
{ "test.battery.start.deep", 0, 0, "UPS.Output.Test", NULL, "2", HU_TYPE_CMD, NULL },
|
||||
{ "test.battery.stop", 0, 0, "UPS.Output.Test", NULL, "3", HU_TYPE_CMD, NULL },
|
||||
{ "load.off.delay", 0, 0, "UPS.Output.DelayBeforeShutdown", NULL, DEFAULT_OFFDELAY, HU_TYPE_CMD, NULL },
|
||||
{ "load.on.delay", 0, 0, "UPS.Output.DelayBeforeStartup", NULL, DEFAULT_ONDELAY, HU_TYPE_CMD, NULL },
|
||||
{ "shutdown.stop", 0, 0, "UPS.Output.DelayBeforeShutdown", NULL, "-1", HU_TYPE_CMD, NULL },
|
||||
{ "shutdown.reboot", 0, 0, "UPS.Output.DelayBeforeReboot", NULL, "10", HU_TYPE_CMD, NULL },
|
||||
{ "beeper.on", 0, 0, "UPS.PowerSummary.AudibleAlarmControl", NULL, "2", HU_TYPE_CMD, NULL },
|
||||
{ "beeper.off", 0, 0, "UPS.PowerSummary.AudibleAlarmControl", NULL, "3", HU_TYPE_CMD, NULL },
|
||||
{ "beeper.enable", 0, 0, "UPS.PowerSummary.AudibleAlarmControl", NULL, "2", HU_TYPE_CMD, NULL },
|
||||
{ "beeper.disable", 0, 0, "UPS.PowerSummary.AudibleAlarmControl", NULL, "1", HU_TYPE_CMD, NULL },
|
||||
{ "beeper.mute", 0, 0, "UPS.PowerSummary.AudibleAlarmControl", NULL, "3", HU_TYPE_CMD, NULL },
|
||||
|
||||
/* end of structure. */
|
||||
{ NULL, 0, 0, NULL, NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
static char *cps_format_model(HIDDevice_t *hd) {
|
||||
return hd->Product;
|
||||
}
|
||||
|
||||
static char *cps_format_mfr(HIDDevice_t *hd) {
|
||||
return hd->Vendor ? hd->Vendor : "CPS";
|
||||
}
|
||||
|
||||
static char *cps_format_serial(HIDDevice_t *hd) {
|
||||
return hd->Serial;
|
||||
}
|
||||
|
||||
/* this function allows the subdriver to "claim" a device: return 1 if
|
||||
* the device is supported by this subdriver, else 0. */
|
||||
static int cps_claim(HIDDevice_t *hd) {
|
||||
|
||||
int status = is_usb_device_supported(cps_usb_device_table, hd->VendorID,
|
||||
hd->ProductID);
|
||||
|
||||
switch (status) {
|
||||
|
||||
case POSSIBLY_SUPPORTED:
|
||||
/* by default, reject, unless the productid option is given */
|
||||
if (getval("productid")) {
|
||||
return 1;
|
||||
}
|
||||
possibly_supported("CyberPower", hd);
|
||||
return 0;
|
||||
|
||||
case SUPPORTED:
|
||||
return 1;
|
||||
|
||||
case NOT_SUPPORTED:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
subdriver_t cps_subdriver = {
|
||||
CPS_HID_VERSION,
|
||||
cps_claim,
|
||||
cps_utab,
|
||||
cps_hid2nut,
|
||||
cps_format_model,
|
||||
cps_format_mfr,
|
||||
cps_format_serial,
|
||||
};
|
||||
30
drivers/cps-hid.h
Normal file
30
drivers/cps-hid.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/* cps-hid.h - subdriver to monitor CPS USB/HID devices with NUT
|
||||
*
|
||||
* Copyright (C)
|
||||
* 2003 - 2005 Arnaud Quette <arnaud.quette@free.fr>
|
||||
* 2005 - 2006 Peter Selinger <selinger@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CPS_HID_H
|
||||
#define CPS_HID_H
|
||||
|
||||
#include "usbhid-ups.h"
|
||||
|
||||
extern subdriver_t cps_subdriver;
|
||||
|
||||
#endif /* CPS_HID_H */
|
||||
700
drivers/dstate-hal.c
Normal file
700
drivers/dstate-hal.c
Normal file
|
|
@ -0,0 +1,700 @@
|
|||
/* dstate-hal.c - Network UPS Tools driver-side state management
|
||||
This is a compatibility interface that encapsulate the HAL bridge
|
||||
into the NUT dstate API for NUT drivers
|
||||
|
||||
Copyright (C) 2006-2007 Arnaud Quette <aquette.dev@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/stat.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "config.h"
|
||||
#include "dstate-hal.h"
|
||||
#include "extstate.h"
|
||||
/* #include "state.h"
|
||||
#include "parseconf.h" */
|
||||
|
||||
#include <hal/libhal.h>
|
||||
|
||||
/* FIXME: export command and RW variables (using the HAL example: addon-cpufreq and macbook addon) */
|
||||
/* beeper.enable, beeper.disable => SetBeeper(bool)
|
||||
beeper.toggle => ToggleBeeper(void)
|
||||
|
||||
org.freedesktop.Hal.Device.UPS.SetSounder (bool)
|
||||
|
||||
|
||||
Shutdown() or ShutOff()
|
||||
shutdown.return
|
||||
shutdown.stayoff
|
||||
shutdown.reboot
|
||||
shutdown.reboot.graceful
|
||||
|
||||
#define UPS_ERROR_GENERAL "GeneralError"
|
||||
#define UPS_ERROR_UNSUPPORTED_FEATURE "FeatureNotSupported"
|
||||
#define UPS_ERROR_PERMISSION_DENIED "PermissionDenied"
|
||||
|
||||
****** implementation *******
|
||||
|
||||
#define DBUS_INTERFACE "org.freedesktop.Hal.Device.UPS"
|
||||
|
||||
if (!libhal_device_claim_interface(halctx, udi, DBUS_INTERFACE,
|
||||
|
||||
" <method name=\"Shutdown\">\n"
|
||||
" <arg name=\"shutdown_type\" direction=\"in\" type=\"s\"/>\n"
|
||||
" <arg name=\"return_code\" direction=\"out\" type=\"i\"/>\n"
|
||||
" </method>\n"
|
||||
|
||||
&dbus_dbus_error)) {
|
||||
fprintf(stderr, "Cannot claim interface: %s", dbus_dbus_error.message);
|
||||
goto Error;
|
||||
}
|
||||
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
* static int sockfd = -1, stale = 1, alarm_active = 0;
|
||||
* static struct st_tree_t *dtree_root = NULL;
|
||||
* static struct conn_t *connhead = NULL;
|
||||
* static struct cmdlist_t *cmdhead = NULL;
|
||||
* static char *sockfn = NULL;
|
||||
* static char status_buf[ST_MAX_VALUE_LEN],
|
||||
* alarm_buf[ST_MAX_VALUE_LEN];
|
||||
*/
|
||||
|
||||
struct ups_handler upsh;
|
||||
|
||||
LibHalContext *halctx = NULL;
|
||||
char *udi;
|
||||
int ac_present = 0; /* 0 = false ; 1 = true */
|
||||
extern char *dbus_methods_introspection;
|
||||
|
||||
static void* runtime_handler(LibHalChangeSet *cs, char* runtime);
|
||||
static void* level_handler(LibHalChangeSet *cs, char* critical_level);
|
||||
static void* battery_type_handler(LibHalChangeSet *cs, char* battery_type);
|
||||
|
||||
/* Structure to lookup between NUT and HAL data */
|
||||
typedef struct {
|
||||
char *nut_name; /* NUT variable name */
|
||||
char *hal_name; /* HAL variable name */
|
||||
int hal_type; /* HAL variable type */
|
||||
void *(*fun)(LibHalChangeSet *cs,
|
||||
char *value); /* conversion function. */
|
||||
} info_lkp_t;
|
||||
|
||||
enum hal_type_t
|
||||
{
|
||||
NONE = 0,
|
||||
HAL_TYPE_INT,
|
||||
HAL_TYPE_BOOL,
|
||||
HAL_TYPE_STRING
|
||||
};
|
||||
|
||||
/* Structure to lookup between NUT commands and HAL/DBus methods */
|
||||
typedef struct {
|
||||
char *nut_name; /* NUT command name */
|
||||
char *hal_name; /* HAL/DBus method name */
|
||||
char *xml_introspection; /* HAL/DBus method introspection */
|
||||
/* FIXME: how to lookup param values between HAL and NUT?? */
|
||||
/* void *(*fun)(LibHalChangeSet *cs,
|
||||
char *value);*/ /* NUT function */
|
||||
} method_lkp_t;
|
||||
|
||||
#if 0
|
||||
/* Data to lookup between NUT commands and HAL/DBus methods
|
||||
* for dstate_addcmd() */
|
||||
static method_lkp_t nut2hal_cmd[] =
|
||||
{
|
||||
/* ups.status is handled by status_set() calls */
|
||||
{
|
||||
"beeper.enable",
|
||||
"SetBeeper",
|
||||
" <method name=\"SetBeeper\">\n"
|
||||
" <arg name=\"beeper_mode\" direction=\"in\" type=\"b\"/>\n"
|
||||
" <arg name=\"return_code\" direction=\"out\" type=\"i\"/>\n"
|
||||
" </method>\n"
|
||||
},
|
||||
|
||||
/* Terminating element */
|
||||
{ NULL, NULL, NULL }
|
||||
};
|
||||
#endif
|
||||
|
||||
/* Data to lookup between NUT and HAL for dstate_setinfo() */
|
||||
static info_lkp_t nut2hal_info[] =
|
||||
{
|
||||
/* ups.status is handled by status_set() calls */
|
||||
{ "battery.charge.low", "battery.charge_level.low", HAL_TYPE_INT, *level_handler },
|
||||
/* { "battery.charge.low", "battery.reporting.low", HAL_TYPE_INT, NULL }, */
|
||||
{ "battery.charge.low", "battery.alarm.design", HAL_TYPE_INT, NULL },
|
||||
|
||||
{ "battery.charge", "battery.charge_level.current", HAL_TYPE_INT, NULL },
|
||||
{ "battery.charge", "battery.charge_level.percentage", HAL_TYPE_INT, NULL },
|
||||
{ "battery.charge", "battery.reporting.current", HAL_TYPE_INT, NULL },
|
||||
{ "battery.charge", "battery.reporting.percentage", HAL_TYPE_INT, NULL },
|
||||
{ "battery.runtime", "battery.remaining_time", HAL_TYPE_INT, *runtime_handler },
|
||||
/* raw version (PbAc) */
|
||||
{ "battery.type", "battery.reporting.technology", HAL_TYPE_STRING, NULL },
|
||||
/* Human readable version */
|
||||
{ "battery.type", "battery.technology", HAL_TYPE_STRING, *battery_type_handler },
|
||||
|
||||
/* AQ note: Not sure it fits! */
|
||||
/* HAL marked as mandatory! */
|
||||
{ "battery.voltage", "battery.voltage.current", HAL_TYPE_INT, NULL },
|
||||
{ "battery.voltage.nominal", "battery.voltage.design", HAL_TYPE_INT, NULL },
|
||||
|
||||
{ "ups.mfr", "battery.vendor", HAL_TYPE_STRING, NULL },
|
||||
{ "ups.model", "battery.model", HAL_TYPE_STRING, NULL },
|
||||
{ "ups.serial", "battery.serial", HAL_TYPE_STRING, NULL },
|
||||
|
||||
/* Terminating element */
|
||||
{ NULL, NULL, NONE, NULL }
|
||||
};
|
||||
|
||||
/* Functions to lookup between NUT and HAL */
|
||||
static info_lkp_t *find_nut_info(const char *nut_varname, info_lkp_t *prev_info_item);
|
||||
|
||||
/* HAL accessors wrappers */
|
||||
void hal_set_string(LibHalChangeSet *cs, const char *key, const char *value);
|
||||
void hal_set_int(LibHalChangeSet *cs, const char *key, const int value);
|
||||
void hal_set_bool(LibHalChangeSet *cs, const char *key, const dbus_bool_t value);
|
||||
int hal_get_int(const char *key);
|
||||
char *hal_get_string(const char *key);
|
||||
|
||||
/* Handle warning charge level according to the critical level */
|
||||
static void* level_handler(LibHalChangeSet *cs, char* critical_level)
|
||||
{
|
||||
/* Magic formula to generate the warning level */
|
||||
int int_critical_level = atoi(critical_level);
|
||||
/* warning level = critical + 1/3 of (100 % - critical level), approx at the leat mod 10 */
|
||||
int int_warning_level = int_critical_level + ((100 - int_critical_level) / 3);
|
||||
int_warning_level -= (int_warning_level % 10);
|
||||
|
||||
/* Set the critical level value */
|
||||
hal_set_int (cs, "battery.charge_level.low", int_critical_level);
|
||||
hal_set_int (cs, "battery.reporting.low", int_critical_level);
|
||||
|
||||
/* Set the warning level value (FIXME: set to 50 % for now) */
|
||||
hal_set_int (cs, "battery.charge_level.warning", int_warning_level);
|
||||
hal_set_int (cs, "battery.reporting.warning", int_warning_level);
|
||||
|
||||
return NULL; /* Nothing to return */
|
||||
}
|
||||
|
||||
/* Handle runtime exposition according to the AC status */
|
||||
static void* runtime_handler(LibHalChangeSet *cs, char* runtime)
|
||||
{
|
||||
if (ac_present == 0) {
|
||||
/* unSet the runtime auto computation and rely upon NUT.battery.runtime*/
|
||||
hal_set_bool (cs, "battery.remaining_time.calculate_per_time", FALSE);
|
||||
|
||||
/* Set the runtime value */
|
||||
hal_set_int (cs, "battery.remaining_time", atoi(runtime));
|
||||
}
|
||||
else {
|
||||
/* Set the runtime auto computation */
|
||||
hal_set_bool (cs, "battery.remaining_time.calculate_per_time", TRUE);
|
||||
|
||||
/* Set the runtime value */
|
||||
hal_set_int (cs, "battery.remaining_time", 0);
|
||||
}
|
||||
return NULL; /* Nothing to return */
|
||||
}
|
||||
|
||||
/* Handle the battery technology reporting */
|
||||
static void* battery_type_handler(LibHalChangeSet *cs, char* battery_type)
|
||||
{
|
||||
if (!strncmp (battery_type, "PbAc", 4)) {
|
||||
hal_set_string(cs, "battery.technology", "lead-acid");
|
||||
}
|
||||
/* FIXME: manage other types (lithium-ion, lithium-polymer,
|
||||
* nickel-metal-hydride, unknown */
|
||||
|
||||
return NULL; /* Nothing to return */
|
||||
}
|
||||
|
||||
/********************************************************************
|
||||
* dstate compatibility interface
|
||||
*******************************************************************/
|
||||
void dstate_init(const char *prog, const char *port)
|
||||
{
|
||||
DBusError dbus_error;
|
||||
LibHalChangeSet *cs;
|
||||
|
||||
dbus_error_init (&dbus_error);
|
||||
|
||||
cs = libhal_device_new_changeset (udi);
|
||||
if (cs == NULL) {
|
||||
fatalx (EXIT_FAILURE, "Cannot initialize changeset");
|
||||
}
|
||||
|
||||
/* UPS always report charge as percent */
|
||||
hal_set_string (cs, "battery.charge_level.unit", "percent");
|
||||
hal_set_string (cs, "battery.reporting.unit", "percent");
|
||||
hal_set_string (cs, "battery.alarm.unit", "percent");
|
||||
|
||||
/* Various UPSs assumptions */
|
||||
/****************************/
|
||||
/* UPS are always rechargeable! */
|
||||
/* FIXME: Check for NUT extension however: ie HID->UPS.PowerSummary.Rechargeable
|
||||
* into battery.rechargeable
|
||||
* or always expose it?
|
||||
*/
|
||||
hal_set_bool (cs, "battery.is_rechargeable", TRUE);
|
||||
|
||||
/* UPS always has a max battery charge of 100 % */
|
||||
hal_set_int (cs, "battery.charge_level.design", 100);
|
||||
hal_set_int (cs, "battery.charge_level.last_full", 100);
|
||||
hal_set_int (cs, "battery.reporting.design", 100);
|
||||
hal_set_int (cs, "battery.reporting.last_full", 100);
|
||||
|
||||
/* NUT always express Voltage in Volts "V" */
|
||||
/* But not all UPSs provide the data
|
||||
* battery.voltage.{design,current} */
|
||||
hal_set_string (cs, "battery.voltage.unit", "V");
|
||||
|
||||
/* UPS always have a battery! */
|
||||
/* Note(AQU): wrong with some solar panel usage, where the UPS */
|
||||
/* is just an energy source switch! */
|
||||
/* FIXME: to be processed (need possible NUT extension) */
|
||||
hal_set_bool (cs, "battery.present", TRUE);
|
||||
|
||||
/* FIXME: can be improved?! (implies "info.recall.vendor") */
|
||||
hal_set_bool (cs, "info.is_recalled", FALSE);
|
||||
|
||||
/* Set generic properties */
|
||||
hal_set_string (cs, "battery.type", "ups");
|
||||
libhal_device_add_capability (halctx, udi, "battery", &dbus_error);
|
||||
libhal_device_add_capability (halctx, udi, "ac_adaptor", &dbus_error);
|
||||
|
||||
/* FIXME: can be improved?! Set granularity (1 %)*/
|
||||
hal_set_int (cs, "battery.charge_level.granularity_1", 1);
|
||||
hal_set_int (cs, "battery.charge_level.granularity_2", 1);
|
||||
hal_set_int (cs, "battery.reporting.granularity_1", 1);
|
||||
hal_set_int (cs, "battery.reporting.granularity_2", 1);
|
||||
|
||||
dbus_error_init (&dbus_error);
|
||||
/* NOTE: commit_changeset won't do IPC if set is empty */
|
||||
libhal_device_commit_changeset (halctx, cs, &dbus_error);
|
||||
libhal_device_free_changeset (cs);
|
||||
|
||||
if (dbus_error_is_set (&dbus_error))
|
||||
dbus_error_free (&dbus_error);
|
||||
}
|
||||
|
||||
|
||||
const char *dstate_getinfo(const char *var)
|
||||
{
|
||||
info_lkp_t *nut2hal_info = find_nut_info(var, NULL);
|
||||
|
||||
/* FIXME: use the data exposed by NUT on the DBus when available */
|
||||
|
||||
if (nut2hal_info != NULL) {
|
||||
|
||||
switch (nut2hal_info->hal_type)
|
||||
{
|
||||
/* case HAL_TYPE_INT:
|
||||
static char value[8];
|
||||
snprintf(value, sizeof(value), "%i", hal_get_int(nut2hal_info->hal_name));
|
||||
return value;
|
||||
case HAL_TYPE_BOOL:
|
||||
hal_set_bool(cs, nut2hal_info->hal_name, TRUE);
|
||||
break;
|
||||
*/
|
||||
case HAL_TYPE_STRING:
|
||||
return hal_get_string(nut2hal_info->hal_name);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int dstate_setinfo(const char *var, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int ret = 1;
|
||||
LibHalChangeSet *cs;
|
||||
DBusError dbus_error;
|
||||
char value[ST_MAX_VALUE_LEN];
|
||||
info_lkp_t *nut2hal_info = NULL, *prev_nut2hal_info = NULL;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(value, sizeof(value), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
cs = libhal_device_new_changeset (udi);
|
||||
if (cs == NULL) {
|
||||
fatalx (EXIT_FAILURE, "Cannot initialize changeset");
|
||||
}
|
||||
|
||||
/* Loop on getting HAL variable(s) matching this NUT variable */
|
||||
while ( (nut2hal_info = find_nut_info(var, prev_nut2hal_info)) != NULL)
|
||||
{
|
||||
upsdebugx(2, "dstate_setinfo: %s => %s (%s)\n", var, nut2hal_info->hal_name, value);
|
||||
|
||||
if (nut2hal_info->fun != NULL)
|
||||
nut2hal_info->fun(cs, value);
|
||||
else {
|
||||
switch (nut2hal_info->hal_type)
|
||||
{
|
||||
case HAL_TYPE_INT:
|
||||
hal_set_int(cs, nut2hal_info->hal_name, atoi(value));
|
||||
break;
|
||||
case HAL_TYPE_BOOL:
|
||||
/* FIXME: howto lookup TRUE/FALSE? */
|
||||
hal_set_bool(cs, nut2hal_info->hal_name, TRUE);
|
||||
break;
|
||||
case HAL_TYPE_STRING:
|
||||
hal_set_string(cs, nut2hal_info->hal_name, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
prev_nut2hal_info = nut2hal_info;
|
||||
}
|
||||
|
||||
dbus_error_init (&dbus_error);
|
||||
/* NOTE: commit_changeset won't do IPC if set is empty */
|
||||
libhal_device_commit_changeset(halctx,cs,&dbus_error);
|
||||
libhal_device_free_changeset (cs);
|
||||
|
||||
if (dbus_error_is_set (&dbus_error))
|
||||
dbus_error_free (&dbus_error);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
int dstate_addenum(const char *var, const char *fmt, ...)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dstate_delinfo(const char *var)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dstate_delcmd(const char *var)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void dstate_setflags(const char *var, int flags)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void dstate_setaux(const char *var, int aux)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void dstate_dataok(void)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void dstate_datastale(void)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void dstate_free(void)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* extrafd: provided for waking up based on the driver's UPS fd */
|
||||
int dstate_poll_fds(struct timeval timeout, int extrafd)
|
||||
{
|
||||
/* drivers expect us to limit the polling rate here */
|
||||
sleep(timeout.tv_sec);
|
||||
|
||||
/* the timeout expired */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* clean out the temp space for a new pass */
|
||||
void status_init(void)
|
||||
{
|
||||
/* Nothing to do */
|
||||
return;
|
||||
}
|
||||
|
||||
/* ups.status element conversion */
|
||||
void status_set(const char *buf)
|
||||
{
|
||||
upsdebugx(2, "status_set: %s", buf);
|
||||
|
||||
/* Note: only usbhid-ups supported devices expose [DIS]CHRG status */
|
||||
/* along with the standard OL (online) / OB (on battery) status! */
|
||||
if ( (strcmp(buf, "DISCHRG") == 0) || (strcmp(buf, "OB") == 0) )
|
||||
{
|
||||
ac_present = 0;
|
||||
}
|
||||
else if ( (strcmp(buf, "CHRG") == 0) || (strcmp(buf, "OL") == 0) )
|
||||
{
|
||||
ac_present = 1;
|
||||
}
|
||||
else
|
||||
upsdebugx(2, "status_set: dropping status %s (not managed)\n", buf);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* write the status_buf into the externally visible dstate storage */
|
||||
void status_commit(void)
|
||||
{
|
||||
LibHalChangeSet *cs;
|
||||
DBusError dbus_error;
|
||||
int curlevel, warnlevel, lowlevel;
|
||||
|
||||
upsdebugx(2, "status_commit");
|
||||
|
||||
cs = libhal_device_new_changeset (udi);
|
||||
if (cs == NULL) {
|
||||
fatalx (EXIT_FAILURE, "Cannot initialize changeset");
|
||||
}
|
||||
|
||||
/* Retrieve the levels */
|
||||
curlevel = hal_get_int ("battery.charge_level.current");
|
||||
warnlevel = hal_get_int ("battery.charge_level.warning");
|
||||
lowlevel = hal_get_int ("battery.charge_level.low");
|
||||
|
||||
/* Set AC present status */
|
||||
/* Note: UPSs are also AC adaptors! */
|
||||
hal_set_bool (cs, "ac_adaptor.present", (ac_present == 0)?FALSE:TRUE);
|
||||
|
||||
/* Set discharging status */
|
||||
hal_set_bool (cs, "battery.rechargeable.is_discharging", (ac_present == 0)?TRUE:FALSE);
|
||||
|
||||
/* Set charging status */
|
||||
if (curlevel != 100)
|
||||
hal_set_bool (cs, "battery.rechargeable.is_charging", (ac_present == 0)?FALSE:TRUE);
|
||||
|
||||
/* Set the battery status (FIXME: are these values valid?) */
|
||||
if (curlevel <= lowlevel)
|
||||
hal_set_string(cs, "battery.charge_level.capacity_state", "critical");
|
||||
else if (curlevel <= warnlevel)
|
||||
hal_set_string(cs, "battery.charge_level.capacity_state", "warning"); /*low?*/
|
||||
else
|
||||
hal_set_string(cs, "battery.charge_level.capacity_state", "ok");
|
||||
|
||||
dbus_error_init (&dbus_error);
|
||||
/* NOTE: commit_changeset won't do IPC if set is empty */
|
||||
libhal_device_commit_changeset (halctx, cs, &dbus_error);
|
||||
libhal_device_free_changeset (cs);
|
||||
|
||||
if (dbus_error_is_set (&dbus_error))
|
||||
dbus_error_free (&dbus_error);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* similar functions for ups.alarm */
|
||||
void alarm_init(void)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void alarm_set(const char *buf)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void alarm_commit(void)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* FIXME: complete and use nut2hal_cmd */
|
||||
/* Register DBus methods, by feeling the methods buffer */
|
||||
void dstate_addcmd(const char *cmdname)
|
||||
{
|
||||
DBusError dbus_error;
|
||||
dbus_error_init (&dbus_error);
|
||||
|
||||
upsdebugx(2, "dstate_addcmd: %s\n", cmdname);
|
||||
|
||||
/* beeper.{on,off} */
|
||||
/* FIXME: how to deal with beeper.toggle */
|
||||
if (!strncasecmp(cmdname, "beeper.disable", 14))
|
||||
{
|
||||
strcat(dbus_methods_introspection,
|
||||
" <method name=\"SetBeeper\">\n"
|
||||
" <arg name=\"beeper_mode\" direction=\"in\" type=\"b\"/>\n"
|
||||
" <arg name=\"return_code\" direction=\"out\" type=\"i\"/>\n"
|
||||
" </method>\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* internal functions
|
||||
*******************************************************************/
|
||||
|
||||
/****************
|
||||
* HAL wrappers *
|
||||
****************/
|
||||
/* Only update HAL string values if there are real changes */
|
||||
void hal_set_string(LibHalChangeSet *cs, const char *key, const char *value)
|
||||
{
|
||||
DBusError dbus_error;
|
||||
char *new_value = NULL;
|
||||
|
||||
upsdebugx(2, "hal_set_string: %s => %s", key, value);
|
||||
|
||||
dbus_error_init(&dbus_error);
|
||||
|
||||
/* Check if the property already exists */
|
||||
if (libhal_device_property_exists (halctx, udi, key, &dbus_error) == TRUE) {
|
||||
|
||||
new_value = libhal_device_get_property_string (halctx, udi,
|
||||
key, &dbus_error);
|
||||
|
||||
/* Check if the value has really changed */
|
||||
if (strcmp(value, new_value))
|
||||
libhal_changeset_set_property_string (cs, key, value);
|
||||
|
||||
/* Free the new_value string */
|
||||
if (new_value != NULL)
|
||||
libhal_free_string (new_value);
|
||||
}
|
||||
else {
|
||||
libhal_changeset_set_property_string (cs, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/* Only update HAL int values if there are real changes */
|
||||
void hal_set_int(LibHalChangeSet *cs, const char *key, const int value)
|
||||
{
|
||||
DBusError dbus_error;
|
||||
int new_value;
|
||||
|
||||
upsdebugx(2, "hal_set_int: %s => %i", key, value);
|
||||
|
||||
dbus_error_init(&dbus_error);
|
||||
|
||||
/* Check if the property already exists */
|
||||
if (libhal_device_property_exists (halctx, udi, key, &dbus_error) == TRUE) {
|
||||
|
||||
new_value = libhal_device_get_property_int (halctx, udi,
|
||||
key, &dbus_error);
|
||||
|
||||
/* Check if the value has really changed */
|
||||
if (value != new_value)
|
||||
libhal_changeset_set_property_int (cs, key, value);
|
||||
}
|
||||
else {
|
||||
libhal_changeset_set_property_int (cs, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
char *hal_get_string(const char *key)
|
||||
{
|
||||
DBusError dbus_error;
|
||||
char *value = NULL;
|
||||
|
||||
upsdebugx(2, "hal_get_string: %s", key);
|
||||
|
||||
dbus_error_init(&dbus_error);
|
||||
|
||||
/* Check if the property already exists */
|
||||
if (libhal_device_property_exists (halctx, udi, key, &dbus_error) == TRUE) {
|
||||
|
||||
value = libhal_device_get_property_string (halctx, udi,
|
||||
key, &dbus_error);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
int hal_get_int(const char *key)
|
||||
{
|
||||
DBusError dbus_error;
|
||||
int value = -1;
|
||||
|
||||
upsdebugx(2, "hal_get_int: %s", key);
|
||||
|
||||
dbus_error_init(&dbus_error);
|
||||
|
||||
/* Check if the property already exists */
|
||||
if (libhal_device_property_exists (halctx, udi, key, &dbus_error) == TRUE) {
|
||||
|
||||
value = libhal_device_get_property_int (halctx, udi,
|
||||
key, &dbus_error);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/* Only update HAL int values if there are real changes */
|
||||
void hal_set_bool(LibHalChangeSet *cs, const char *key, const dbus_bool_t value)
|
||||
{
|
||||
DBusError dbus_error;
|
||||
dbus_bool_t new_value;
|
||||
|
||||
upsdebugx(2, "hal_set_bool: %s => %s", key, (value==TRUE)?"true":"false");
|
||||
|
||||
dbus_error_init(&dbus_error);
|
||||
|
||||
/* Check if the property already exists */
|
||||
if (libhal_device_property_exists (halctx, udi, key, &dbus_error) == TRUE) {
|
||||
|
||||
new_value = libhal_device_get_property_bool (halctx, udi,
|
||||
key, &dbus_error);
|
||||
|
||||
/* Check if the value has really changed */
|
||||
if (value != new_value)
|
||||
libhal_changeset_set_property_bool (cs, key, value);
|
||||
}
|
||||
else {
|
||||
libhal_changeset_set_property_bool (cs, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/* find the next info element definition in info array
|
||||
* that matches nut_varname, and that is after prev_info_item
|
||||
* if specified.
|
||||
* Note that 1 nut item can matches several HAL items
|
||||
*/
|
||||
static info_lkp_t *find_nut_info(const char *nut_varname, info_lkp_t *prev_info_item)
|
||||
{
|
||||
info_lkp_t *info_item;
|
||||
|
||||
upsdebugx(2, "find_nut_info: looking up => %s\n", nut_varname);
|
||||
|
||||
if (prev_info_item != NULL) {
|
||||
/* Start from the item following prev_info_item */
|
||||
info_item = ++prev_info_item;
|
||||
}
|
||||
else {
|
||||
info_item = nut2hal_info;
|
||||
}
|
||||
|
||||
for ( ; info_item != NULL && info_item->nut_name != NULL ; info_item++) {
|
||||
|
||||
if (!strcasecmp(info_item->nut_name, nut_varname))
|
||||
return info_item;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
92
drivers/dstate-hal.h
Normal file
92
drivers/dstate-hal.h
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/* dstate-hal.h - Network UPS Tools driver-side state management
|
||||
|
||||
Copyright (C) 2006 Arnaud Quette <aquette.dev@gmail.com>
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
#ifndef DSTATE_HAL_H_SEEN
|
||||
#define DSTATE_HAL_H_SEEN 1
|
||||
|
||||
#include "attribute.h"
|
||||
|
||||
/*#include "parseconf.h"*/
|
||||
#include "upshandler.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <dbus/dbus-glib.h>
|
||||
#include <dbus/dbus-glib-lowlevel.h>
|
||||
#include <hal/libhal.h>
|
||||
|
||||
#define DS_LISTEN_BACKLOG 16
|
||||
#define DS_MAX_READ 256 /* don't read forever from upsd */
|
||||
|
||||
/* HAL specific */
|
||||
#define DBUS_INTERFACE "org.freedesktop.Hal.Device.UPS"
|
||||
|
||||
DBusHandlerResult dbus_filter_function(DBusConnection *connection,
|
||||
DBusMessage *message,
|
||||
void *user_data);
|
||||
|
||||
gboolean dbus_init_local (void);
|
||||
|
||||
#define HAL_WARNING
|
||||
|
||||
/* track client connections */
|
||||
/* struct conn_t {
|
||||
* int fd;
|
||||
* PCONF_CTX_t ctx;
|
||||
* void *next;
|
||||
*};
|
||||
*/
|
||||
extern struct ups_handler upsh;
|
||||
|
||||
void dstate_init(const char *prog, const char *port);
|
||||
int dstate_poll_fds(struct timeval timeout, int extrafd);
|
||||
int dstate_setinfo(const char *var, const char *fmt, ...)
|
||||
__attribute__ ((__format__ (__printf__, 2, 3)));
|
||||
int dstate_addenum(const char *var, const char *fmt, ...)
|
||||
__attribute__ ((__format__ (__printf__, 2, 3)));
|
||||
void dstate_setflags(const char *var, int flags);
|
||||
void dstate_setaux(const char *var, int aux);
|
||||
const char *dstate_getinfo(const char *var);
|
||||
void dstate_addcmd(const char *cmdname);
|
||||
int dstate_delinfo(const char *var);
|
||||
int dstate_delenum(const char *var, const char *val);
|
||||
int dstate_delcmd(const char *cmd);
|
||||
void dstate_free(void);
|
||||
const struct st_tree_t *dstate_getroot(void);
|
||||
const struct cmdlist_t *dstate_getcmdlist(void);
|
||||
|
||||
void dstate_dataok(void);
|
||||
void dstate_datastale(void);
|
||||
|
||||
int dstate_is_stale(void);
|
||||
|
||||
/* clean out the temp space for a new pass */
|
||||
void status_init(void);
|
||||
|
||||
/* add a status element */
|
||||
void status_set(const char *buf);
|
||||
|
||||
/* write the temporary status_buf into ups.status */
|
||||
void status_commit(void);
|
||||
|
||||
/* similar functions for ups.alarm */
|
||||
void alarm_init(void);
|
||||
void alarm_set(const char *buf);
|
||||
void alarm_commit(void);
|
||||
|
||||
#endif /* DSTATE_HAL_H_SEEN */
|
||||
845
drivers/dstate.c
Normal file
845
drivers/dstate.c
Normal file
|
|
@ -0,0 +1,845 @@
|
|||
/* dstate.c - Network UPS Tools driver-side state management
|
||||
|
||||
Copyright (C)
|
||||
2003 Russell Kroll <rkroll@exploits.org>
|
||||
2008 Arjen de Korte <adkorte-guest@alioth.debian.org>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/stat.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "dstate.h"
|
||||
#include "state.h"
|
||||
#include "parseconf.h"
|
||||
|
||||
static int sockfd = -1, stale = 1, alarm_active = 0;
|
||||
static char *sockfn = NULL;
|
||||
static char status_buf[ST_MAX_VALUE_LEN], alarm_buf[ST_MAX_VALUE_LEN];
|
||||
static struct st_tree_t *dtree_root = NULL;
|
||||
static struct conn_t *connhead = NULL;
|
||||
static struct cmdlist_t *cmdhead = NULL;
|
||||
|
||||
struct ups_handler upsh;
|
||||
|
||||
/* this may be a frequent stumbling point for new users, so be verbose here */
|
||||
static void sock_fail(const char *fn)
|
||||
{
|
||||
int sockerr;
|
||||
struct passwd *user;
|
||||
|
||||
/* save this so it doesn't get overwritten */
|
||||
sockerr = errno;
|
||||
|
||||
/* dispense with the usual upslog stuff since we have stderr here */
|
||||
|
||||
printf("\nFatal error: unable to create listener socket\n\n");
|
||||
printf("bind %s failed: %s\n", fn, strerror(sockerr));
|
||||
|
||||
user = getpwuid(getuid());
|
||||
|
||||
if (!user) {
|
||||
fatal_with_errno(EXIT_FAILURE, "getpwuid");
|
||||
}
|
||||
|
||||
/* deal with some common problems */
|
||||
switch (errno)
|
||||
{
|
||||
case EACCES:
|
||||
printf("\nCurrent user: %s (UID %d)\n\n",
|
||||
user->pw_name, (int)user->pw_uid);
|
||||
|
||||
printf("Things to try:\n\n");
|
||||
printf(" - set different owners or permissions on %s\n\n",
|
||||
dflt_statepath());
|
||||
printf(" - run this as some other user "
|
||||
"(try -u <username>)\n");
|
||||
break;
|
||||
|
||||
case ENOENT:
|
||||
printf("\nThings to try:\n\n");
|
||||
printf(" - mkdir %s\n", dflt_statepath());
|
||||
break;
|
||||
|
||||
case ENOTDIR:
|
||||
printf("\nThings to try:\n\n");
|
||||
printf(" - rm %s\n\n", dflt_statepath());
|
||||
printf(" - mkdir %s\n", dflt_statepath());
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* there - that wasn't so bad. every helpful line of code here
|
||||
* prevents one more "help me" mail to the list a year from now
|
||||
*/
|
||||
|
||||
printf("\n");
|
||||
fatalx(EXIT_FAILURE, "Exiting.");
|
||||
}
|
||||
|
||||
static int sock_open(const char *fn)
|
||||
{
|
||||
int ret, fd;
|
||||
struct sockaddr_un ssaddr;
|
||||
|
||||
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
|
||||
if (fd < 0) {
|
||||
fatal_with_errno(EXIT_FAILURE, "Can't create a unix domain socket");
|
||||
}
|
||||
|
||||
/* keep this around for the unlink() when exiting */
|
||||
sockfn = xstrdup(fn);
|
||||
|
||||
ssaddr.sun_family = AF_UNIX;
|
||||
snprintf(ssaddr.sun_path, sizeof(ssaddr.sun_path), "%s", sockfn);
|
||||
|
||||
unlink(sockfn);
|
||||
|
||||
/* group gets access so upsd can be a different user but same group */
|
||||
umask(0007);
|
||||
|
||||
ret = bind(fd, (struct sockaddr *) &ssaddr, sizeof ssaddr);
|
||||
|
||||
if (ret < 0) {
|
||||
sock_fail(sockfn);
|
||||
}
|
||||
|
||||
ret = chmod(sockfn, 0660);
|
||||
|
||||
if (ret < 0) {
|
||||
fatal_with_errno(EXIT_FAILURE, "chmod(%s, 0660) failed", sockfn);
|
||||
}
|
||||
|
||||
ret = listen(fd, DS_LISTEN_BACKLOG);
|
||||
|
||||
if (ret < 0) {
|
||||
fatal_with_errno(EXIT_FAILURE, "listen(%d, %d) failed", fd, DS_LISTEN_BACKLOG);
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
static void sock_disconnect(struct conn_t *conn)
|
||||
{
|
||||
close(conn->fd);
|
||||
|
||||
pconf_finish(&conn->ctx);
|
||||
|
||||
if (conn->prev) {
|
||||
conn->prev->next = conn->next;
|
||||
} else {
|
||||
connhead = conn->next;
|
||||
}
|
||||
|
||||
if (conn->next) {
|
||||
conn->next->prev = conn->prev;
|
||||
} else {
|
||||
/* conntail = conn->prev; */
|
||||
}
|
||||
|
||||
free(conn);
|
||||
}
|
||||
|
||||
static void send_to_all(const char *fmt, ...)
|
||||
{
|
||||
int ret;
|
||||
char buf[ST_SOCK_BUF_LEN];
|
||||
va_list ap;
|
||||
struct conn_t *conn, *cnext;
|
||||
|
||||
va_start(ap, fmt);
|
||||
ret = vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (ret < 1) {
|
||||
upsdebugx(2, "%s: nothing to write", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
upsdebugx(5, "%s: %.*s", __func__, ret-1, buf);
|
||||
|
||||
for (conn = connhead; conn; conn = cnext) {
|
||||
cnext = conn->next;
|
||||
|
||||
ret = write(conn->fd, buf, strlen(buf));
|
||||
|
||||
if (ret != (int)strlen(buf)) {
|
||||
upsdebugx(2, "write %d bytes to socket %d failed", (int)strlen(buf), conn->fd);
|
||||
sock_disconnect(conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int send_to_one(struct conn_t *conn, const char *fmt, ...)
|
||||
{
|
||||
int ret;
|
||||
va_list ap;
|
||||
char buf[ST_SOCK_BUF_LEN];
|
||||
|
||||
va_start(ap, fmt);
|
||||
ret = vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (ret < 1) {
|
||||
upsdebugx(2, "%s: nothing to write", __func__);
|
||||
return 1;
|
||||
}
|
||||
|
||||
upsdebugx(5, "%s: %.*s", __func__, ret-1, buf);
|
||||
|
||||
ret = write(conn->fd, buf, strlen(buf));
|
||||
|
||||
if (ret != (int)strlen(buf)) {
|
||||
upsdebugx(2, "write %d bytes to socket %d failed", (int)strlen(buf), conn->fd);
|
||||
sock_disconnect(conn);
|
||||
return 0; /* failed */
|
||||
}
|
||||
|
||||
return 1; /* OK */
|
||||
}
|
||||
|
||||
static void sock_connect(int sock)
|
||||
{
|
||||
int fd, ret;
|
||||
struct conn_t *conn;
|
||||
struct sockaddr_un sa;
|
||||
socklen_t salen;
|
||||
|
||||
salen = sizeof(sa);
|
||||
fd = accept(sock, (struct sockaddr *) &sa, &salen);
|
||||
|
||||
if (fd < 0) {
|
||||
upslog_with_errno(LOG_ERR, "accept on unix fd failed");
|
||||
return;
|
||||
}
|
||||
|
||||
/* enable nonblocking I/O */
|
||||
|
||||
ret = fcntl(fd, F_GETFL, 0);
|
||||
|
||||
if (ret < 0) {
|
||||
upslog_with_errno(LOG_ERR, "fcntl get on unix fd failed");
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = fcntl(fd, F_SETFL, ret | O_NDELAY);
|
||||
|
||||
if (ret < 0) {
|
||||
upslog_with_errno(LOG_ERR, "fcntl set O_NDELAY on unix fd failed");
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
conn = xcalloc(1, sizeof(*conn));
|
||||
conn->fd = fd;
|
||||
|
||||
pconf_init(&conn->ctx, NULL);
|
||||
|
||||
if (connhead) {
|
||||
conn->next = connhead;
|
||||
connhead->prev = conn;
|
||||
}
|
||||
|
||||
connhead = conn;
|
||||
|
||||
upsdebugx(3, "new connection on fd %d", fd);
|
||||
}
|
||||
|
||||
static int st_tree_dump_conn(struct st_tree_t *node, struct conn_t *conn)
|
||||
{
|
||||
int ret;
|
||||
struct enum_t *etmp;
|
||||
|
||||
if (!node) {
|
||||
return 1; /* not an error */
|
||||
}
|
||||
|
||||
if (node->left) {
|
||||
ret = st_tree_dump_conn(node->left, conn);
|
||||
|
||||
if (!ret) {
|
||||
return 0; /* write failed in the child */
|
||||
}
|
||||
}
|
||||
|
||||
if (!send_to_one(conn, "SETINFO %s \"%s\"\n", node->var, node->val)) {
|
||||
return 0; /* write failed, bail out */
|
||||
}
|
||||
|
||||
/* send any enums */
|
||||
for (etmp = node->enum_list; etmp; etmp = etmp->next) {
|
||||
if (!send_to_one(conn, "ADDENUM %s \"%s\"\n", node->var, etmp->val)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* provide any auxiliary data */
|
||||
if (node->aux) {
|
||||
if (!send_to_one(conn, "SETAUX %s %d\n", node->var, node->aux)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* finally report any flags */
|
||||
if (node->flags) {
|
||||
char flist[SMALLBUF];
|
||||
|
||||
/* build the list */
|
||||
snprintf(flist, sizeof(flist), "%s", node->var);
|
||||
|
||||
if (node->flags & ST_FLAG_RW) {
|
||||
snprintfcat(flist, sizeof(flist), " RW");
|
||||
}
|
||||
if (node->flags & ST_FLAG_STRING) {
|
||||
snprintfcat(flist, sizeof(flist), " STRING");
|
||||
}
|
||||
|
||||
send_to_one(conn, "SETFLAGS %s\n", flist);
|
||||
}
|
||||
|
||||
if (node->right) {
|
||||
return st_tree_dump_conn(node->right, conn);
|
||||
}
|
||||
|
||||
return 1; /* everything's OK here ... */
|
||||
}
|
||||
|
||||
static int cmd_dump_conn(struct conn_t *conn)
|
||||
{
|
||||
struct cmdlist_t *cmd;
|
||||
|
||||
for (cmd = cmdhead; cmd; cmd = cmd->next) {
|
||||
if (!send_to_one(conn, "ADDCMD %s\n", cmd->name)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int sock_arg(struct conn_t *conn, int numarg, char **arg)
|
||||
{
|
||||
if (numarg < 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!strcasecmp(arg[0], "DUMPALL")) {
|
||||
|
||||
/* first thing: the staleness flag */
|
||||
if ((stale == 1) && !send_to_one(conn, "DATASTALE\n")) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!st_tree_dump_conn(dtree_root, conn)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!cmd_dump_conn(conn)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ((stale == 0) && !send_to_one(conn, "DATAOK\n")) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
send_to_one(conn, "DUMPDONE\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcasecmp(arg[0], "PING")) {
|
||||
send_to_one(conn, "PONG\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (numarg < 2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* INSTCMD <cmdname> [<value>]*/
|
||||
if (!strcasecmp(arg[0], "INSTCMD")) {
|
||||
|
||||
/* try the new handler first if present */
|
||||
if (upsh.instcmd) {
|
||||
if (numarg > 2) {
|
||||
upsh.instcmd(arg[1], arg[2]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
upsh.instcmd(arg[1], NULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
upslogx(LOG_NOTICE, "Got INSTCMD, but driver lacks a handler");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (numarg < 3) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* SET <var> <value> */
|
||||
if (!strcasecmp(arg[0], "SET")) {
|
||||
|
||||
/* try the new handler first if present */
|
||||
if (upsh.setvar) {
|
||||
upsh.setvar(arg[1], arg[2]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
upslogx(LOG_NOTICE, "Got SET, but driver lacks a handler");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* unknown */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sock_read(struct conn_t *conn)
|
||||
{
|
||||
int i, ret;
|
||||
char buf[SMALLBUF];
|
||||
|
||||
ret = read(conn->fd, buf, sizeof(buf));
|
||||
|
||||
if (ret < 0) {
|
||||
switch(errno)
|
||||
{
|
||||
case EINTR:
|
||||
case EAGAIN:
|
||||
return;
|
||||
|
||||
default:
|
||||
sock_disconnect(conn);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < ret; i++) {
|
||||
|
||||
switch(pconf_char(&conn->ctx, buf[i]))
|
||||
{
|
||||
case 0: /* nothing to parse yet */
|
||||
continue;
|
||||
|
||||
case 1: /* try to use it, and complain about unknown commands */
|
||||
if (!sock_arg(conn, conn->ctx.numargs, conn->ctx.arglist)) {
|
||||
size_t arg;
|
||||
|
||||
upslogx(LOG_INFO, "Unknown command on socket: ");
|
||||
|
||||
for (arg = 0; arg < conn->ctx.numargs; arg++) {
|
||||
upslogx(LOG_INFO, "arg %d: %s", (int)arg, conn->ctx.arglist[arg]);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
|
||||
default: /* nothing parsed */
|
||||
upslogx(LOG_NOTICE, "Parse error on sock: %s", conn->ctx.errmsg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void sock_close(void)
|
||||
{
|
||||
struct conn_t *conn, *cnext;
|
||||
|
||||
if (sockfd != -1) {
|
||||
close(sockfd);
|
||||
sockfd = -1;
|
||||
|
||||
if (sockfn) {
|
||||
unlink(sockfn);
|
||||
free(sockfn);
|
||||
sockfn = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
for (conn = connhead; conn; conn = cnext) {
|
||||
cnext = conn->next;
|
||||
sock_disconnect(conn);
|
||||
}
|
||||
|
||||
connhead = NULL;
|
||||
/* conntail = NULL; */
|
||||
}
|
||||
|
||||
/* interface */
|
||||
|
||||
void dstate_init(const char *prog, const char *port)
|
||||
{
|
||||
char sockname[SMALLBUF];
|
||||
|
||||
/* do this here for now */
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
if (port) {
|
||||
snprintf(sockname, sizeof(sockname), "%s/%s-%s", dflt_statepath(), prog, port);
|
||||
} else {
|
||||
snprintf(sockname, sizeof(sockname), "%s/%s", dflt_statepath(), prog);
|
||||
}
|
||||
|
||||
sockfd = sock_open(sockname);
|
||||
|
||||
upsdebugx(2, "dstate_init: sock %s open on fd %d", sockname, sockfd);
|
||||
}
|
||||
|
||||
/* returns 1 if timeout expired or data is available on UPS fd, 0 otherwise */
|
||||
int dstate_poll_fds(struct timeval timeout, int extrafd)
|
||||
{
|
||||
int ret, maxfd, overrun = 0;
|
||||
fd_set rfds;
|
||||
struct timeval now;
|
||||
struct conn_t *conn, *cnext;
|
||||
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(sockfd, &rfds);
|
||||
|
||||
maxfd = sockfd;
|
||||
|
||||
if (extrafd != -1) {
|
||||
FD_SET(extrafd, &rfds);
|
||||
|
||||
if (extrafd > maxfd) {
|
||||
maxfd = extrafd;
|
||||
}
|
||||
}
|
||||
|
||||
for (conn = connhead; conn; conn = conn->next) {
|
||||
FD_SET(conn->fd, &rfds);
|
||||
|
||||
if (conn->fd > maxfd) {
|
||||
maxfd = conn->fd;
|
||||
}
|
||||
}
|
||||
|
||||
gettimeofday(&now, NULL);
|
||||
|
||||
/* number of microseconds should always be positive */
|
||||
if (timeout.tv_usec < now.tv_usec) {
|
||||
timeout.tv_sec -= 1;
|
||||
timeout.tv_usec += 1000000;
|
||||
}
|
||||
|
||||
if (timeout.tv_sec < now.tv_sec) {
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = 0;
|
||||
overrun = 1; /* no time left */
|
||||
} else {
|
||||
timeout.tv_sec -= now.tv_sec;
|
||||
timeout.tv_usec -= now.tv_usec;
|
||||
}
|
||||
|
||||
ret = select(maxfd + 1, &rfds, NULL, NULL, &timeout);
|
||||
|
||||
if (ret == 0) {
|
||||
return 1; /* timer expired */
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
switch (errno)
|
||||
{
|
||||
case EINTR:
|
||||
case EAGAIN:
|
||||
/* ignore interruptions from signals */
|
||||
break;
|
||||
|
||||
default:
|
||||
upslog_with_errno(LOG_ERR, "select unix sockets failed");
|
||||
}
|
||||
|
||||
return overrun;
|
||||
}
|
||||
|
||||
if (FD_ISSET(sockfd, &rfds)) {
|
||||
sock_connect(sockfd);
|
||||
}
|
||||
|
||||
for (conn = connhead; conn; conn = cnext) {
|
||||
cnext = conn->next;
|
||||
|
||||
if (FD_ISSET(conn->fd, &rfds)) {
|
||||
sock_read(conn);
|
||||
}
|
||||
}
|
||||
|
||||
/* tell the caller if that fd woke up */
|
||||
if ((extrafd != -1) && (FD_ISSET(extrafd, &rfds))) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return overrun;
|
||||
}
|
||||
|
||||
int dstate_setinfo(const char *var, const char *fmt, ...)
|
||||
{
|
||||
int ret;
|
||||
char value[ST_MAX_VALUE_LEN];
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(value, sizeof(value), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
ret = state_setinfo(&dtree_root, var, value);
|
||||
|
||||
if (ret == 1) {
|
||||
send_to_all("SETINFO %s \"%s\"\n", var, value);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int dstate_addenum(const char *var, const char *fmt, ...)
|
||||
{
|
||||
int ret;
|
||||
char value[ST_MAX_VALUE_LEN];
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(value, sizeof(value), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
ret = state_addenum(dtree_root, var, value);
|
||||
|
||||
if (ret == 1) {
|
||||
send_to_all("ADDENUM %s \"%s\"\n", var, value);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void dstate_setflags(const char *var, int flags)
|
||||
{
|
||||
struct st_tree_t *sttmp;
|
||||
char flist[SMALLBUF];
|
||||
|
||||
/* find the dtree node for var */
|
||||
sttmp = state_tree_find(dtree_root, var);
|
||||
|
||||
if (!sttmp) {
|
||||
upslogx(LOG_ERR, "dstate_setflags: base variable (%s) does not exist", var);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sttmp->flags == flags) {
|
||||
return; /* no change */
|
||||
}
|
||||
|
||||
sttmp->flags = flags;
|
||||
|
||||
/* build the list */
|
||||
snprintf(flist, sizeof(flist), "%s", var);
|
||||
|
||||
if (flags & ST_FLAG_RW) {
|
||||
snprintfcat(flist, sizeof(flist), " RW");
|
||||
}
|
||||
|
||||
if (flags & ST_FLAG_STRING) {
|
||||
snprintfcat(flist, sizeof(flist), " STRING");
|
||||
}
|
||||
|
||||
/* update listeners */
|
||||
send_to_all("SETFLAGS %s\n", flist);
|
||||
}
|
||||
|
||||
void dstate_setaux(const char *var, int aux)
|
||||
{
|
||||
struct st_tree_t *sttmp;
|
||||
|
||||
/* find the dtree node for var */
|
||||
sttmp = state_tree_find(dtree_root, var);
|
||||
|
||||
if (!sttmp) {
|
||||
upslogx(LOG_ERR, "dstate_setaux: base variable (%s) does not exist", var);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sttmp->aux == aux) {
|
||||
return; /* no change */
|
||||
}
|
||||
|
||||
sttmp->aux = aux;
|
||||
|
||||
/* update listeners */
|
||||
send_to_all("SETAUX %s %d\n", var, aux);
|
||||
}
|
||||
|
||||
const char *dstate_getinfo(const char *var)
|
||||
{
|
||||
return state_getinfo(dtree_root, var);
|
||||
}
|
||||
|
||||
void dstate_addcmd(const char *cmdname)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = state_addcmd(&cmdhead, cmdname);
|
||||
|
||||
/* update listeners */
|
||||
if (ret == 1) {
|
||||
send_to_all("ADDCMD %s\n", cmdname);
|
||||
}
|
||||
}
|
||||
|
||||
int dstate_delinfo(const char *var)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = state_delinfo(&dtree_root, var);
|
||||
|
||||
/* update listeners */
|
||||
if (ret == 1) {
|
||||
send_to_all("DELINFO %s\n", var);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int dstate_delenum(const char *var, const char *val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = state_delenum(dtree_root, var, val);
|
||||
|
||||
/* update listeners */
|
||||
if (ret == 1) {
|
||||
send_to_all("DELENUM %s \"%s\"\n", var, val);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int dstate_delcmd(const char *cmd)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = state_delcmd(&cmdhead, cmd);
|
||||
|
||||
/* update listeners */
|
||||
if (ret == 1) {
|
||||
send_to_all("DELCMD %s\n", cmd);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void dstate_free(void)
|
||||
{
|
||||
state_infofree(dtree_root);
|
||||
dtree_root = NULL;
|
||||
|
||||
state_cmdfree(cmdhead);
|
||||
cmdhead = NULL;
|
||||
|
||||
sock_close();
|
||||
}
|
||||
|
||||
const struct st_tree_t *dstate_getroot(void)
|
||||
{
|
||||
return dtree_root;
|
||||
}
|
||||
|
||||
const struct cmdlist_t *dstate_getcmdlist(void)
|
||||
{
|
||||
return cmdhead;
|
||||
}
|
||||
|
||||
void dstate_dataok(void)
|
||||
{
|
||||
if (stale == 1) {
|
||||
stale = 0;
|
||||
send_to_all("DATAOK\n");
|
||||
}
|
||||
}
|
||||
|
||||
void dstate_datastale(void)
|
||||
{
|
||||
if (stale == 0) {
|
||||
stale = 1;
|
||||
send_to_all("DATASTALE\n");
|
||||
}
|
||||
}
|
||||
|
||||
int dstate_is_stale(void)
|
||||
{
|
||||
return stale;
|
||||
}
|
||||
|
||||
/* ups.status management functions - reducing duplication in the drivers */
|
||||
|
||||
/* clean out the temp space for a new pass */
|
||||
void status_init(void)
|
||||
{
|
||||
memset(status_buf, 0, sizeof(status_buf));
|
||||
}
|
||||
|
||||
/* add a status element */
|
||||
void status_set(const char *buf)
|
||||
{
|
||||
/* separate with a space if multiple elements are present */
|
||||
if (strlen(status_buf) > 0) {
|
||||
snprintfcat(status_buf, sizeof(status_buf), " %s", buf);
|
||||
} else {
|
||||
snprintfcat(status_buf, sizeof(status_buf), "%s", buf);
|
||||
}
|
||||
}
|
||||
|
||||
/* write the status_buf into the externally visible dstate storage */
|
||||
void status_commit(void)
|
||||
{
|
||||
if (alarm_active) {
|
||||
dstate_setinfo("ups.status", "ALARM %s", status_buf);
|
||||
} else {
|
||||
dstate_setinfo("ups.status", "%s", status_buf);
|
||||
}
|
||||
}
|
||||
|
||||
/* similar handlers for ups.alarm */
|
||||
|
||||
void alarm_init(void)
|
||||
{
|
||||
memset(alarm_buf, 0, sizeof(alarm_buf));
|
||||
}
|
||||
|
||||
void alarm_set(const char *buf)
|
||||
{
|
||||
if (strlen(alarm_buf) > 0) {
|
||||
snprintfcat(alarm_buf, sizeof(alarm_buf), " %s", buf);
|
||||
} else {
|
||||
snprintfcat(alarm_buf, sizeof(alarm_buf), "%s", buf);
|
||||
}
|
||||
}
|
||||
|
||||
/* write the status_buf into the info array */
|
||||
void alarm_commit(void)
|
||||
{
|
||||
if (strlen(alarm_buf) > 0) {
|
||||
dstate_setinfo("ups.alarm", "%s", alarm_buf);
|
||||
alarm_active = 1;
|
||||
} else {
|
||||
dstate_delinfo("ups.alarm");
|
||||
alarm_active = 0;
|
||||
}
|
||||
}
|
||||
77
drivers/dstate.h
Normal file
77
drivers/dstate.h
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/* dstate.h - Network UPS Tools driver-side state management
|
||||
|
||||
Copyright (C) 2003 Russell Kroll <rkroll@exploits.org>
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
#ifndef DSTATE_H_SEEN
|
||||
#define DSTATE_H_SEEN 1
|
||||
|
||||
#include "attribute.h"
|
||||
|
||||
#include "parseconf.h"
|
||||
#include "upshandler.h"
|
||||
|
||||
#define DS_LISTEN_BACKLOG 16
|
||||
#define DS_MAX_READ 256 /* don't read forever from upsd */
|
||||
|
||||
/* track client connections */
|
||||
struct conn_t {
|
||||
int fd;
|
||||
PCONF_CTX_t ctx;
|
||||
struct conn_t *prev;
|
||||
struct conn_t *next;
|
||||
};
|
||||
|
||||
extern struct ups_handler upsh;
|
||||
|
||||
void dstate_init(const char *prog, const char *port);
|
||||
int dstate_poll_fds(struct timeval timeout, int extrafd);
|
||||
int dstate_setinfo(const char *var, const char *fmt, ...)
|
||||
__attribute__ ((__format__ (__printf__, 2, 3)));
|
||||
int dstate_addenum(const char *var, const char *fmt, ...)
|
||||
__attribute__ ((__format__ (__printf__, 2, 3)));
|
||||
void dstate_setflags(const char *var, int flags);
|
||||
void dstate_setaux(const char *var, int aux);
|
||||
const char *dstate_getinfo(const char *var);
|
||||
void dstate_addcmd(const char *cmdname);
|
||||
int dstate_delinfo(const char *var);
|
||||
int dstate_delenum(const char *var, const char *val);
|
||||
int dstate_delcmd(const char *cmd);
|
||||
void dstate_free(void);
|
||||
const struct st_tree_t *dstate_getroot(void);
|
||||
const struct cmdlist_t *dstate_getcmdlist(void);
|
||||
|
||||
void dstate_dataok(void);
|
||||
void dstate_datastale(void);
|
||||
|
||||
int dstate_is_stale(void);
|
||||
|
||||
/* clean out the temp space for a new pass */
|
||||
void status_init(void);
|
||||
|
||||
/* add a status element */
|
||||
void status_set(const char *buf);
|
||||
|
||||
/* write the temporary status_buf into ups.status */
|
||||
void status_commit(void);
|
||||
|
||||
/* similar functions for ups.alarm */
|
||||
void alarm_init(void);
|
||||
void alarm_set(const char *buf);
|
||||
void alarm_commit(void);
|
||||
|
||||
#endif /* DSTATE_H_SEEN */
|
||||
484
drivers/dummy-ups.c
Normal file
484
drivers/dummy-ups.c
Normal file
|
|
@ -0,0 +1,484 @@
|
|||
/* dummy-ups.c - NUT testing driver and repeater
|
||||
|
||||
Copyright (C)
|
||||
2005 - 2009 Arnaud Quette <http://arnaud.quette.free.fr/contact.html>
|
||||
|
||||
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:
|
||||
* - separate the code between dummy and repeater/meta
|
||||
* - for repeater/meta:
|
||||
* * add support for instant commands and setvar
|
||||
* - for dummy:
|
||||
* * variable/value enforcement using cmdvartab for testing
|
||||
* the variable existance, and possible values
|
||||
* * allow variable creation on the fly (using upsrw)
|
||||
* * poll the "port" file for change
|
||||
*/
|
||||
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "main.h"
|
||||
#include "parseconf.h"
|
||||
#include "upsclient.h"
|
||||
#include "dummy-ups.h"
|
||||
|
||||
#define DRIVER_NAME "Dummy UPS driver"
|
||||
#define DRIVER_VERSION "0.10"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Arnaud Quette <arnaud.quette@gmail.com>",
|
||||
DRV_STABLE,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
#define MODE_UNKNOWN 0
|
||||
#define MODE_DUMMY 1 /* use the embedded defintion or a definition file */
|
||||
#define MODE_REPEATER 2 /* use libupsclient to repeat an UPS */
|
||||
#define MODE_META 3 /* consolidate data from several UPSs (TBS) */
|
||||
|
||||
int mode=MODE_UNKNOWN;
|
||||
|
||||
/* parseconf context, for dummy mode using a file */
|
||||
PCONF_CTX_t *ctx=NULL;
|
||||
time_t next_update = -1;
|
||||
|
||||
#define MAX_STRING_SIZE 128
|
||||
|
||||
static int setvar(const char *varname, const char *val);
|
||||
static int instcmd(const char *cmdname, const char *extra);
|
||||
static int parse_data_file(int upsfd);
|
||||
static dummy_info_t *find_info(const char *varname);
|
||||
static int is_valid_data(const char* varname);
|
||||
static int is_valid_value(const char* varname, const char *value);
|
||||
/* libupsclient update */
|
||||
static int upsclient_update_vars(void);
|
||||
|
||||
/* connection information */
|
||||
static char *client_upsname = NULL, *hostname = NULL;
|
||||
static UPSCONN_t *ups = NULL;
|
||||
int port;
|
||||
|
||||
/* Driver functions */
|
||||
|
||||
void upsdrv_initinfo(void)
|
||||
{
|
||||
dummy_info_t *item;
|
||||
|
||||
switch (mode) {
|
||||
case MODE_DUMMY:
|
||||
/* Initialise basic essential variables */
|
||||
for ( item = nut_data ; item->info_type != NULL ; item++ ) {
|
||||
if (item->drv_flags & DU_FLAG_INIT) {
|
||||
dstate_setinfo(item->info_type, "%s", item->default_value);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/* Now get user's defined variables */
|
||||
if (parse_data_file(upsfd) < 0)
|
||||
upslogx(LOG_NOTICE, "Unable to parse the definition file %s", device_path);
|
||||
|
||||
/* Initialize handler */
|
||||
upsh.setvar = setvar;
|
||||
|
||||
dstate_dataok();
|
||||
break;
|
||||
case MODE_META:
|
||||
case MODE_REPEATER:
|
||||
/* Obtain the target name */
|
||||
if (upscli_splitname(device_path, &client_upsname, &hostname, &port) != 0) {
|
||||
fatalx(EXIT_FAILURE, "Error: invalid UPS definition.\nRequired format: upsname[@hostname[:port]]");
|
||||
}
|
||||
/* Connect to the target */
|
||||
ups = xmalloc(sizeof(*ups));
|
||||
if (upscli_connect(ups, hostname, port, UPSCLI_CONN_TRYSSL) < 0) {
|
||||
fatalx(EXIT_FAILURE, "Error: %s", upscli_strerror(ups));
|
||||
}
|
||||
else {
|
||||
upsdebugx(1, "Connected to %s@%s", client_upsname, hostname);
|
||||
}
|
||||
if (upsclient_update_vars() < 0) {
|
||||
/* check for an old upsd */
|
||||
if (upscli_upserror(ups) == UPSCLI_ERR_UNKCOMMAND) {
|
||||
fatalx(EXIT_FAILURE, "Error: upsd is too old to support this query");
|
||||
}
|
||||
fatalx(EXIT_FAILURE, "Error: %s", upscli_strerror(ups));
|
||||
}
|
||||
/* FIXME: commands and settables! */
|
||||
break;
|
||||
default:
|
||||
case MODE_UNKNOWN:
|
||||
fatalx(EXIT_FAILURE, "no suitable definition found!");
|
||||
break;
|
||||
}
|
||||
upsh.instcmd = instcmd;
|
||||
|
||||
dstate_addcmd("load.off");
|
||||
}
|
||||
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
upsdebugx(1, "upsdrv_updateinfo...");
|
||||
|
||||
sleep(1);
|
||||
|
||||
switch (mode) {
|
||||
case MODE_DUMMY:
|
||||
/* Now get user's defined variables */
|
||||
if (parse_data_file(upsfd) >= 0)
|
||||
dstate_dataok();
|
||||
break;
|
||||
case MODE_META:
|
||||
case MODE_REPEATER:
|
||||
if (upsclient_update_vars() > 0)
|
||||
dstate_dataok();
|
||||
else {
|
||||
/* try to reconnect */
|
||||
upscli_disconnect(ups);
|
||||
if (upscli_connect(ups, hostname, port, UPSCLI_CONN_TRYSSL) < 0) {
|
||||
upsdebugx(1, "Error reconnecting: %s", upscli_strerror(ups));
|
||||
}
|
||||
else {
|
||||
upsdebugx(1, "Reconnected");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
fatalx(EXIT_FAILURE, "shutdown not supported");
|
||||
}
|
||||
|
||||
static int instcmd(const char *cmdname, const char *extra)
|
||||
{
|
||||
/*
|
||||
if (!strcasecmp(cmdname, "test.battery.stop")) {
|
||||
ser_send_buf(upsfd, ...);
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
*/
|
||||
upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
}
|
||||
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
}
|
||||
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
}
|
||||
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
/* check the running mode... */
|
||||
if (strchr(device_path, '@')) {
|
||||
upsdebugx(1, "Repeater mode");
|
||||
mode = MODE_REPEATER;
|
||||
dstate_setinfo("driver.parameter.mode", "repeater");
|
||||
/* if there is at least one more => MODE_META... */
|
||||
}
|
||||
else {
|
||||
upsdebugx(1, "Dummy mode");
|
||||
mode = MODE_DUMMY;
|
||||
dstate_setinfo("driver.parameter.mode", "dummy");
|
||||
}
|
||||
}
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
if ( (mode == MODE_META) || (mode == MODE_REPEATER) ) {
|
||||
if (ups) {
|
||||
upscli_disconnect(ups);
|
||||
}
|
||||
|
||||
if (ctx) {
|
||||
pconf_finish(ctx);
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
free(client_upsname);
|
||||
free(hostname);
|
||||
free(ups);
|
||||
}
|
||||
}
|
||||
|
||||
static int setvar(const char *varname, const char *val)
|
||||
{
|
||||
dummy_info_t *item;
|
||||
|
||||
upsdebugx(2, "entering setvar(%s, %s)", varname, val);
|
||||
|
||||
if (!strncmp(varname, "ups.status", 10)) {
|
||||
status_init();
|
||||
/* FIXME: split and check values (support multiple values), à la usbhid-ups */
|
||||
status_set(val);
|
||||
status_commit();
|
||||
|
||||
return STAT_SET_HANDLED;
|
||||
}
|
||||
|
||||
/* Check variable validity */
|
||||
if (!is_valid_data(varname)) {
|
||||
upsdebugx(2, "setvar: invalid variable name (%s)", varname);
|
||||
|
||||
return STAT_SET_UNKNOWN;
|
||||
}
|
||||
|
||||
/* Check value validity */
|
||||
if (!is_valid_value(varname, val)) {
|
||||
upsdebugx(2, "setvar: invalid value (%s) for variable (%s)", val, varname);
|
||||
|
||||
return STAT_SET_UNKNOWN;
|
||||
}
|
||||
|
||||
dstate_setinfo(varname, "%s", val);
|
||||
|
||||
if ( (item = find_info(varname)) != NULL) {
|
||||
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);
|
||||
}
|
||||
|
||||
return STAT_SET_HANDLED;
|
||||
}
|
||||
|
||||
/*************************************************/
|
||||
/* Support functions */
|
||||
/*************************************************/
|
||||
|
||||
static int upsclient_update_vars(void)
|
||||
{
|
||||
int ret;
|
||||
unsigned int numq, numa;
|
||||
const char *query[4];
|
||||
char **answer;
|
||||
|
||||
query[0] = "VAR";
|
||||
query[1] = client_upsname;
|
||||
numq = 2;
|
||||
|
||||
ret = upscli_list_start(ups, numq, query);
|
||||
|
||||
if (ret < 0) {
|
||||
upsdebugx(1, "Error: %s (%i)", upscli_strerror(ups), upscli_upserror(ups));
|
||||
return ret;
|
||||
}
|
||||
|
||||
while (upscli_list_next(ups, numq, query, &numa, &answer) == 1) {
|
||||
|
||||
/* VAR <upsname> <varname> <val> */
|
||||
if (numa < 4) {
|
||||
upsdebugx(1, "Error: insufficient data (got %d args, need at least 4)", numa);
|
||||
}
|
||||
|
||||
upsdebugx(5, "Received: %s %s %s %s",
|
||||
answer[0], answer[1], answer[2], answer[3]);
|
||||
|
||||
/* do not override the driver collection */
|
||||
if (strncmp(answer[2], "driver.", 7))
|
||||
setvar(answer[2], answer[3]);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* find info element definition in info array */
|
||||
static dummy_info_t *find_info(const char *varname)
|
||||
{
|
||||
dummy_info_t *item;
|
||||
|
||||
for ( item = nut_data ; item->info_type != NULL ; item++ ) {
|
||||
if (!strcasecmp(item->info_type, varname))
|
||||
return item;
|
||||
}
|
||||
|
||||
upsdebugx(2, "find_info: unknown variable: %s\n", varname);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* check if data exists in our data table */
|
||||
static int is_valid_data(const char* varname)
|
||||
{
|
||||
dummy_info_t *item;
|
||||
|
||||
if ( (item = find_info(varname)) != NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* FIXME: we need to have the full data set before
|
||||
* enforcing controls! */
|
||||
|
||||
upsdebugx(1, "Unknown data. Commiting anyway...");
|
||||
return 1;
|
||||
/* return 0;*/
|
||||
}
|
||||
|
||||
/* check if data's value validity */
|
||||
static int is_valid_value(const char* varname, const char *value)
|
||||
{
|
||||
dummy_info_t *item;
|
||||
|
||||
if ( (item = find_info(varname)) != NULL) {
|
||||
/* FIXME: test enum or bound against value */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* FIXME: we need to have the full data set before
|
||||
* enforcing controls! */
|
||||
|
||||
upsdebugx(1, "Unknown data. Commiting value anyway...");
|
||||
return 1;
|
||||
/* return 0;*/
|
||||
}
|
||||
|
||||
/* called for fatal errors in parseconf like malloc failures */
|
||||
static void upsconf_err(const char *errmsg)
|
||||
{
|
||||
upslogx(LOG_ERR, "Fatal error in parseconf(ups.conf): %s", errmsg);
|
||||
}
|
||||
|
||||
/* for dummy mode
|
||||
* parse the definition file and process its content
|
||||
*/
|
||||
static int parse_data_file(int upsfd)
|
||||
{
|
||||
char fn[SMALLBUF];
|
||||
char *ptr, *var_value;
|
||||
int value_args = 0, counter;
|
||||
time_t now;
|
||||
|
||||
time(&now);
|
||||
|
||||
upsdebugx(1, "entering parse_data_file()");
|
||||
|
||||
if (now < next_update) {
|
||||
upsdebugx(1, "leaving (paused)...");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* initialise everything, to loop back at the beginning of the file */
|
||||
if (ctx == NULL) {
|
||||
|
||||
ctx = (PCONF_CTX_t *)xmalloc(sizeof(PCONF_CTX_t));
|
||||
|
||||
if (device_path[0] == '/')
|
||||
snprintf(fn, sizeof(fn), "%s", device_path);
|
||||
else
|
||||
snprintf(fn, sizeof(fn), "%s/%s", confpath(), device_path);
|
||||
|
||||
pconf_init(ctx, upsconf_err);
|
||||
|
||||
if (!pconf_file_begin(ctx, fn))
|
||||
fatalx(EXIT_FAILURE, "Can't open dummy-ups definition file %s: %s",
|
||||
fn, ctx->errmsg);
|
||||
}
|
||||
|
||||
/* reset the next call time, so that we can loop back on the file
|
||||
* if there is no blocking action (ie TIMER) until the end of the file */
|
||||
next_update = -1;
|
||||
|
||||
/* now start or continue parsing... */
|
||||
while (pconf_file_next(ctx)) {
|
||||
|
||||
if (pconf_parse_error(ctx)) {
|
||||
upsdebugx(2, "Parse error: %s:%d: %s",
|
||||
fn, ctx->linenum, ctx->errmsg);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check if we have something to process */
|
||||
if (ctx->numargs < 1)
|
||||
continue;
|
||||
|
||||
/* process actions (only "TIMER" ATM) */
|
||||
if (!strncmp(ctx->arglist[0], "TIMER", 5)) {
|
||||
/* TIMER <seconds> will wait "seconds" before
|
||||
* continuing the parsing */
|
||||
int delay = atoi (ctx->arglist[1]);
|
||||
time(&next_update);
|
||||
next_update += delay;
|
||||
upsdebugx(1, "suspending execution for %i seconds...", delay);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Remove the ":" after the variable name */
|
||||
if ((ptr = strchr(ctx->arglist[0], ':')) != NULL)
|
||||
*ptr = '\0';
|
||||
|
||||
upsdebugx(3, "parse_data_file: variable \"%s\" with %d args", ctx->arglist[0], (int)ctx->numargs);
|
||||
|
||||
/* skip the driver.* collection data */
|
||||
if (!strncmp(ctx->arglist[0], "driver.", 7)) {
|
||||
upsdebugx(2, "parse_data_file: skipping %s", ctx->arglist[0]);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* From there, we get varname in arg[0], and values in other arg[1...x] */
|
||||
/* FIXME: iteration on arg[2, 3, ...]
|
||||
if ST_FLAG_STRING => all args are the value
|
||||
if ups.status, each arg is a value to be set (ie OB LB) + check against enum
|
||||
else int/float values need to be check against bound/enum
|
||||
*/
|
||||
var_value = (char*) xmalloc(MAX_STRING_SIZE);
|
||||
for (counter = 1, value_args = ctx->numargs ; counter < value_args ; counter++) {
|
||||
if (counter != 1) /* don't append the first space separator */
|
||||
strncat(var_value, " ", MAX_STRING_SIZE);
|
||||
strncat(var_value, ctx->arglist[counter], MAX_STRING_SIZE);
|
||||
}
|
||||
|
||||
/* special handler for status */
|
||||
if (!strncmp( ctx->arglist[0], "ups.status", 10)) {
|
||||
status_init();
|
||||
setvar(ctx->arglist[0], var_value);
|
||||
status_commit();
|
||||
}
|
||||
else {
|
||||
|
||||
if (setvar(ctx->arglist[0], var_value) == STAT_SET_UNKNOWN) {
|
||||
upsdebugx(2, "parse_data_file: can't add \"%s\" with value \"%s\"\nError: %s",
|
||||
ctx->arglist[0], var_value, ctx->errmsg);
|
||||
}
|
||||
else {
|
||||
upsdebugx(3, "parse_data_file: added \"%s\" with value \"%s\"",
|
||||
ctx->arglist[0], var_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* cleanup parseconf if there is no pending action */
|
||||
if (next_update == -1) {
|
||||
pconf_finish(ctx);
|
||||
free(ctx);
|
||||
ctx=NULL;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
224
drivers/dummy-ups.h
Normal file
224
drivers/dummy-ups.h
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
/* dummy-ups.h - NUT testing driver and repeater
|
||||
|
||||
Copyright (C)
|
||||
2005 - 2009 Arnaud Quette <http://arnaud.quette.free.fr/contact.html>
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
/* This file list all valid data with their type and info.
|
||||
* this are then enable through a definition file, specified
|
||||
* as the "port" parameter (only the file name, not the path).
|
||||
* The format of this file is the same as an upsc dump:
|
||||
* <varname>: <value>
|
||||
* ...
|
||||
* Once the driver is loaded:
|
||||
* - change the value by using upsrw
|
||||
* - ?you can add new variables and change variable values by
|
||||
* editing the definition file?
|
||||
*/
|
||||
|
||||
/* from usbhid-ups.h */
|
||||
/* --------------------------------------------------------------- */
|
||||
/* Struct & data for ups.status processing */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
typedef struct {
|
||||
char *status_str; /* ups.status string */
|
||||
int status_value; /* ups.status value */
|
||||
} status_lkp_t;
|
||||
|
||||
#define STATUS_CAL 1 /* calibration */
|
||||
#define STATUS_TRIM 2 /* SmartTrim */
|
||||
#define STATUS_BOOST 4 /* SmartBoost */
|
||||
#define STATUS_OL 8 /* on line */
|
||||
#define STATUS_OB 16 /* on battery */
|
||||
#define STATUS_OVER 32 /* overload */
|
||||
#define STATUS_LB 64 /* low battery */
|
||||
#define STATUS_RB 128 /* replace battery */
|
||||
#define STATUS_BYPASS 256 /* on bypass */
|
||||
#define STATUS_OFF 512 /* ups is off */
|
||||
#define STATUS_CHRG 1024 /* charging */
|
||||
#define STATUS_DISCHRG 2048 /* discharging */
|
||||
|
||||
status_lkp_t status_info[] = {
|
||||
{ "CAL", STATUS_CAL },
|
||||
{ "TRIM", STATUS_TRIM },
|
||||
{ "BOOST", STATUS_BOOST },
|
||||
{ "OL", STATUS_OL },
|
||||
{ "OB", STATUS_OB },
|
||||
{ "OVER", STATUS_OVER },
|
||||
{ "LB", STATUS_LB },
|
||||
{ "RB", STATUS_RB },
|
||||
{ "BYPASS", STATUS_BYPASS },
|
||||
{ "OFF", STATUS_OFF },
|
||||
{ "CHRG", STATUS_CHRG },
|
||||
{ "DISCHRG", STATUS_DISCHRG },
|
||||
{ "NULL", 0 },
|
||||
};
|
||||
/* from usbhid-ups.h */
|
||||
|
||||
typedef struct {
|
||||
char hid_value; /* HID value */
|
||||
char *nut_value; /* NUT value */
|
||||
} info_enum_t;
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* Structure containing information about how to get/set data */
|
||||
/* from/to the UPS and convert these to/from NUT standard */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
typedef struct {
|
||||
char *info_type; /* NUT variable name */
|
||||
int info_flags; /* NUT flags (to set in addinfo) */
|
||||
float info_len; /* if ST_FLAG_STRING: length of the string */
|
||||
/* if HU_TYPE_CMD: command value ; multiplier (or max len) otherwise */
|
||||
char *default_value; /* if HU_FLAG_ABSENT: default value ; format otherwise */
|
||||
int drv_flags; /* */
|
||||
char **var_values; /* all possible values for this variable (allows to check data...) */
|
||||
/* FIXME: "void *" so we can have bound or enum */
|
||||
/* interpreter interpret; */ /* FFE: interpreter fct, NULL if not needed */
|
||||
} dummy_info_t;
|
||||
|
||||
/* data flags */
|
||||
#define DU_FLAG_NONE 0
|
||||
#define DU_FLAG_INIT 1 /* intialy show element to upsd */
|
||||
#define DU_TYPE_CMD 2
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* Data table (all possible info from NUT, then enable upon cong */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
/* FIXME: need to enforce value check with enum or bounds */
|
||||
dummy_info_t nut_data[] =
|
||||
{
|
||||
/* Essential variables, loaded before parsing the definition file */
|
||||
{ "ups.mfr", ST_FLAG_STRING | ST_FLAG_RW, 32, "Dummy Manufacturer", DU_FLAG_INIT, NULL },
|
||||
{ "ups.model", ST_FLAG_STRING | ST_FLAG_RW, 32, "Dummy UPS", DU_FLAG_INIT, NULL },
|
||||
{ "ups.status", ST_FLAG_STRING | ST_FLAG_RW, 32, "OL", DU_FLAG_INIT, NULL },
|
||||
/* Other known variables */
|
||||
{ "ups.serial", ST_FLAG_STRING | ST_FLAG_RW, 32, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "ups.load", ST_FLAG_RW, 32, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "ups.alarm", ST_FLAG_STRING | ST_FLAG_RW, 32, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "ups.time", ST_FLAG_STRING | ST_FLAG_RW, 16, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "ups.date", ST_FLAG_STRING | ST_FLAG_RW, 16, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "ups.mfr.date", ST_FLAG_STRING | ST_FLAG_RW, 16, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "ups.serial", ST_FLAG_STRING | ST_FLAG_RW, 16, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "ups.firmware", ST_FLAG_STRING | ST_FLAG_RW, 16, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "ups.firmware.aux", ST_FLAG_STRING | ST_FLAG_RW, 16, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "ups.temperature", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "ups.id", ST_FLAG_STRING | ST_FLAG_RW, 16, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "ups.delay.start", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "ups.delay.reboot", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "ups.delay.shutdown", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "ups.test.interval", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "ups.test.result", ST_FLAG_STRING | ST_FLAG_RW, 16, NULL, DU_FLAG_NONE, NULL },
|
||||
/*
|
||||
ups.display.language
|
||||
ups.contacts
|
||||
ups.power
|
||||
ups.power.nominal
|
||||
*/
|
||||
{ "input.voltage", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "input.voltage.maximum", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "input.voltage.minimum", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "input.voltage.nominal", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "input.transfer.reason", ST_FLAG_STRING | ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "input.transfer.low", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "input.transfer.high", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "input.sensitivity", ST_FLAG_STRING | ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "input.quality", ST_FLAG_STRING | ST_FLAG_RW, 6, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "input.frequency", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "input.transfer.boost.low", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "input.transfer.boost.high", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "input.transfer.trim.low", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "input.transfer.trim.high", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "output.voltage", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "output.frequency", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "output.current", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
/*
|
||||
output.voltage.nominal
|
||||
*/
|
||||
{ "battery.charge", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "battery.charge.low", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "battery.voltage", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "battery.current", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "battery.runtime", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "battery.runtime.low", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "battery.charge.restart", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "battery.temperature", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
{ "battery.voltage.nominal", ST_FLAG_RW, 1, NULL, DU_FLAG_NONE, NULL },
|
||||
/*
|
||||
battery.alarm.threshold
|
||||
battery.date
|
||||
battery.packs
|
||||
battery.packs.bad
|
||||
|
||||
ambient.temperature
|
||||
ambient.temperature.alarm
|
||||
ambient.temperature.high
|
||||
ambient.temperature.low
|
||||
ambient.humidity
|
||||
ambient.humidity.alarm
|
||||
ambient.humidity.high
|
||||
ambient.humidity.low
|
||||
|
||||
FIXME: how to manage these?
|
||||
outlet.n.id
|
||||
outlet.n.desc
|
||||
outlet.n.switch
|
||||
outlet.n.status
|
||||
outlet.n.switchable
|
||||
outlet.n.autoswitch.charge.low
|
||||
outlet.n.delay.shutdown
|
||||
outlet.n.delay.start
|
||||
|
||||
driver.name
|
||||
driver.version
|
||||
driver.version.internal | Internal driver version | 1.23.45 |
|
||||
driver.parameter.xxx
|
||||
driver.flag.xxx
|
||||
|
||||
No need for these!
|
||||
server.info
|
||||
server.version
|
||||
|
||||
Cmds
|
||||
load.off
|
||||
load.on
|
||||
shutdown.return
|
||||
shutdown.stayoff
|
||||
shutdown.stop
|
||||
shutdown.reboot
|
||||
shutdown.reboot.graceful
|
||||
test.panel.start
|
||||
test.panel.stop
|
||||
test.failure.start
|
||||
test.failure.stop
|
||||
test.battery.start
|
||||
test.battery.stop
|
||||
calibrate.start
|
||||
calibrate.stop
|
||||
bypass.start
|
||||
bypass.stop
|
||||
reset.input.minmax
|
||||
reset.watchdog
|
||||
beeper.enable
|
||||
beeper.disable
|
||||
*/
|
||||
|
||||
/* end of structure. */
|
||||
{ NULL, 0, 0, NULL, DU_FLAG_NONE, NULL }
|
||||
};
|
||||
204
drivers/eaton-mib.c
Normal file
204
drivers/eaton-mib.c
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
/* eaton-mib.c - data to monitor Eaton Aphel PDUs (Basic and Complex)
|
||||
*
|
||||
* Copyright (C) 2008
|
||||
* Arnaud Quette <ArnaudQuette@Eaton.com>
|
||||
*
|
||||
* Sponsored by Eaton <http://www.eaton.com>
|
||||
* and MGE Office Protection Systems <http://www.mgeops.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "eaton-mib.h"
|
||||
|
||||
#define EATON_APHEL_MIB_VERSION "0.4"
|
||||
|
||||
/* APHEL-GENESIS-II-MIB (monitored ePDU)
|
||||
* *************************************
|
||||
* Note: we should also be able to support this one using netxml-ups!
|
||||
*/
|
||||
|
||||
#define APHEL1_OID_MIB ".1.3.6.1.4.1.17373"
|
||||
#define APHEL1_OID_MODEL_NAME ".1.3.6.1.4.1.17373.3.1.1.0"
|
||||
#define APHEL1_OID_FIRMREV ".1.3.6.1.4.1.17373.3.1.2.0"
|
||||
#define APHEL1_OID_DEVICE_NAME ".1.3.6.1.4.1.17373.3.1.3.0"
|
||||
#define APHEL1_OID_UNIT_MACADDR ".1.3.6.1.4.1.17373.3.1.4.0"
|
||||
/* needs concat .<outlet-index>.0 */
|
||||
#define APHEL1_OID_OUTLET_CURRENT ".1.3.6.1.4.1.17373.3.2"
|
||||
|
||||
/* Snmp2NUT lookup table for GenesisII MIB */
|
||||
static snmp_info_t eaton_aphel_genesisII_mib[] = {
|
||||
/* Device page */
|
||||
{ "device.mfr", ST_FLAG_STRING, SU_INFOSIZE, NULL, "EATON | Powerware",
|
||||
SU_FLAG_STATIC | SU_FLAG_ABSENT | SU_FLAG_OK, NULL, NULL },
|
||||
{ "device.model", ST_FLAG_STRING, SU_INFOSIZE, APHEL1_OID_MODEL_NAME,
|
||||
"Eaton Powerware ePDU Monitored", SU_FLAG_STATIC | SU_FLAG_OK, NULL, NULL },
|
||||
{ "device.type", ST_FLAG_STRING, SU_INFOSIZE, NULL, "pdu",
|
||||
SU_FLAG_STATIC | SU_FLAG_ABSENT | SU_FLAG_OK, NULL, NULL },
|
||||
|
||||
/* UPS page */
|
||||
{ "ups.mfr", ST_FLAG_STRING, SU_INFOSIZE, NULL, "EATON | Powerware",
|
||||
SU_FLAG_STATIC | SU_FLAG_ABSENT | SU_FLAG_OK, NULL, NULL },
|
||||
{ "ups.model", ST_FLAG_STRING, SU_INFOSIZE, APHEL1_OID_MODEL_NAME,
|
||||
"Generic SNMP PDU", SU_FLAG_STATIC | SU_FLAG_OK, NULL, NULL },
|
||||
{ "ups.id", ST_FLAG_STRING, SU_INFOSIZE, APHEL1_OID_DEVICE_NAME,
|
||||
"unknown", SU_FLAG_STATIC | SU_FLAG_OK, NULL, NULL },
|
||||
{ "ups.firmware", ST_FLAG_STRING, SU_INFOSIZE, APHEL1_OID_FIRMREV, "",
|
||||
SU_FLAG_STATIC | SU_FLAG_OK, NULL, NULL },
|
||||
{ "ups.type", ST_FLAG_STRING, SU_INFOSIZE, NULL, "pdu",
|
||||
SU_FLAG_STATIC | SU_FLAG_ABSENT | SU_FLAG_OK, NULL, NULL },
|
||||
{ "ups.macaddr", ST_FLAG_STRING, SU_INFOSIZE, APHEL1_OID_UNIT_MACADDR, "unknown",
|
||||
0, NULL, NULL },
|
||||
|
||||
/* Outlet page */
|
||||
/* we can't use template since there is no counterpart to outlet.count */
|
||||
{ "outlet.1.current", 0, 0.001, APHEL1_OID_OUTLET_CURRENT ".1.0", NULL, SU_FLAG_NEGINVALID, NULL, NULL },
|
||||
{ "outlet.2.current", 0, 0.001, APHEL1_OID_OUTLET_CURRENT ".2.0", NULL, SU_FLAG_NEGINVALID, NULL, NULL },
|
||||
{ "outlet.3.current", 0, 0.001, APHEL1_OID_OUTLET_CURRENT ".3.0", NULL, SU_FLAG_NEGINVALID, NULL, NULL },
|
||||
{ "outlet.4.current", 0, 0.001, APHEL1_OID_OUTLET_CURRENT ".4.0", NULL, SU_FLAG_NEGINVALID, NULL, NULL },
|
||||
{ "outlet.5.current", 0, 0.001, APHEL1_OID_OUTLET_CURRENT ".5.0", NULL, SU_FLAG_NEGINVALID, NULL, NULL },
|
||||
{ "outlet.6.current", 0, 0.001, APHEL1_OID_OUTLET_CURRENT ".6.0", NULL, SU_FLAG_NEGINVALID, NULL, NULL },
|
||||
{ "outlet.7.current", 0, 0.001, APHEL1_OID_OUTLET_CURRENT ".7.0", NULL, SU_FLAG_NEGINVALID, NULL, NULL },
|
||||
{ "outlet.8.current", 0, 0.001, APHEL1_OID_OUTLET_CURRENT ".8.0", NULL, SU_FLAG_NEGINVALID, NULL, NULL },
|
||||
|
||||
/* end of structure. */
|
||||
{ NULL, 0, 0, NULL, NULL, 0, NULL, NULL }
|
||||
};
|
||||
|
||||
|
||||
/* APHEL PDU-MIB - Revelation MIB (Managed ePDU)
|
||||
* ********************************************* */
|
||||
|
||||
#define AR_BASE_OID ".1.3.6.1.4.1.534.6.6.6"
|
||||
|
||||
#define APHEL2_OID_MODEL_NAME AR_OID_MODEL_NAME
|
||||
|
||||
/* Common Aphel / Raritan declaration */
|
||||
|
||||
#define AR_OID_MODEL_NAME AR_BASE_OID ".1.1.12.0"
|
||||
#define AR_OID_DEVICE_NAME AR_BASE_OID ".1.1.13.0"
|
||||
#define AR_OID_FIRMREV AR_BASE_OID ".1.1.1.0"
|
||||
#define AR_OID_SERIAL AR_BASE_OID ".1.1.2.0"
|
||||
#define AR_OID_UNIT_MACADDR AR_BASE_OID ".1.1.6.0"
|
||||
|
||||
#define AR_OID_UNIT_CURRENT AR_BASE_OID ".1.3.1.1"
|
||||
#define AR_OID_UNIT_VOLTAGE AR_BASE_OID ".1.3.1.2"
|
||||
#define AR_OID_UNIT_ACTIVEPOWER AR_BASE_OID ".1.3.1.3"
|
||||
#define AR_OID_UNIT_APPARENTPOWER AR_BASE_OID ".1.3.1.4"
|
||||
#define AR_OID_UNIT_CPUTEMPERATURE AR_BASE_OID ".1.3.1.5.0"
|
||||
|
||||
#define AR_OID_OUTLET_INDEX AR_BASE_OID ".1.2.2.1.1"
|
||||
#define AR_OID_OUTLET_NAME AR_BASE_OID ".1.2.2.1.2"
|
||||
#define AR_OID_OUTLET_STATUS AR_BASE_OID ".1.2.2.1.3"
|
||||
|
||||
static info_lkp_t outlet_status_info[] = {
|
||||
{ -1, "error" },
|
||||
{ 0, "off" },
|
||||
{ 1, "on" },
|
||||
{ 2, "cycling" }, /* transitional status */
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
#define DO_OFF 0
|
||||
#define DO_ON 1
|
||||
#define DO_CYCLE 2
|
||||
|
||||
#define AR_OID_OUTLET_COUNT AR_BASE_OID ".1.2.1.0"
|
||||
#define AR_OID_OUTLET_CURRENT AR_BASE_OID ".1.2.2.1.4"
|
||||
#define AR_OID_OUTLET_MAXCURRENT AR_BASE_OID ".1.2.2.1.5"
|
||||
#define AR_OID_OUTLET_VOLTAGE AR_BASE_OID ".1.2.2.1.6"
|
||||
#define AR_OID_OUTLET_ACTIVEPOWER AR_BASE_OID ".1.2.2.1.7"
|
||||
#define AR_OID_OUTLET_APPARENTPOWER AR_BASE_OID ".1.2.2.1.8"
|
||||
#define AR_OID_OUTLET_POWERFACTOR AR_BASE_OID ".1.2.2.1.9"
|
||||
|
||||
/* Snmp2NUT lookup table for Eaton Revelation MIB */
|
||||
static snmp_info_t eaton_aphel_revelation_mib[] = {
|
||||
/* Device page */
|
||||
{ "device.mfr", ST_FLAG_STRING, SU_INFOSIZE, NULL, "EATON | Powerware",
|
||||
SU_FLAG_STATIC | SU_FLAG_ABSENT | SU_FLAG_OK, NULL, NULL },
|
||||
{ "device.model", ST_FLAG_STRING, SU_INFOSIZE, AR_OID_MODEL_NAME,
|
||||
"Eaton Powerware ePDU Managed", SU_FLAG_STATIC | SU_FLAG_OK, NULL, NULL },
|
||||
{ "device.serial", ST_FLAG_STRING, SU_INFOSIZE, AR_OID_SERIAL, "",
|
||||
SU_FLAG_STATIC | SU_FLAG_OK, NULL, NULL },
|
||||
{ "device.type", ST_FLAG_STRING, SU_INFOSIZE, NULL, "pdu",
|
||||
SU_FLAG_STATIC | SU_FLAG_ABSENT | SU_FLAG_OK, NULL, NULL },
|
||||
|
||||
/* UPS page */
|
||||
{ "ups.mfr", ST_FLAG_STRING, SU_INFOSIZE, NULL, "EATON | Powerware",
|
||||
SU_FLAG_STATIC | SU_FLAG_ABSENT | SU_FLAG_OK, NULL, NULL },
|
||||
{ "ups.model", ST_FLAG_STRING, SU_INFOSIZE, AR_OID_MODEL_NAME,
|
||||
"Generic SNMP PDU", SU_FLAG_STATIC | SU_FLAG_OK, NULL, NULL },
|
||||
{ "ups.id", ST_FLAG_STRING, SU_INFOSIZE, AR_OID_DEVICE_NAME,
|
||||
"unknown", SU_FLAG_STATIC | SU_FLAG_OK, NULL, NULL },
|
||||
{ "ups.serial", ST_FLAG_STRING, SU_INFOSIZE, AR_OID_SERIAL, "",
|
||||
SU_FLAG_STATIC | SU_FLAG_OK, NULL, NULL },
|
||||
{ "ups.firmware", ST_FLAG_STRING, SU_INFOSIZE, AR_OID_FIRMREV, "",
|
||||
SU_FLAG_STATIC | SU_FLAG_OK, NULL },
|
||||
{ "ups.type", ST_FLAG_STRING, SU_INFOSIZE, NULL, "pdu",
|
||||
SU_FLAG_STATIC | SU_FLAG_ABSENT | SU_FLAG_OK, NULL, NULL },
|
||||
{ "ups.macaddr", ST_FLAG_STRING, SU_INFOSIZE, AR_OID_UNIT_MACADDR, "",
|
||||
SU_FLAG_STATIC | SU_FLAG_OK, NULL, NULL },
|
||||
{ "ups.temperature", 0, 1, AR_OID_UNIT_CPUTEMPERATURE, NULL, 0, NULL, NULL },
|
||||
|
||||
/* Outlet page */
|
||||
{ "outlet.id", 0, 1, NULL, "0", SU_FLAG_STATIC | SU_FLAG_ABSENT | SU_FLAG_OK, NULL },
|
||||
{ "outlet.desc", ST_FLAG_RW | ST_FLAG_STRING, 20, NULL, "All outlets",
|
||||
SU_FLAG_STATIC | SU_FLAG_ABSENT | SU_FLAG_OK, NULL },
|
||||
{ "outlet.count", 0, 1, AR_OID_OUTLET_COUNT, "0", 0, NULL },
|
||||
{ "outlet.current", 0, 0.001, AR_OID_UNIT_CURRENT ".0", NULL, 0, NULL, NULL },
|
||||
{ "outlet.voltage", 0, 0.001, AR_OID_UNIT_VOLTAGE ".0", NULL, 0, NULL, NULL },
|
||||
{ "outlet.realpower", 0, 1.0, AR_OID_UNIT_ACTIVEPOWER ".0", NULL, 0, NULL, NULL },
|
||||
{ "outlet.power", 0, 1.0, AR_OID_UNIT_APPARENTPOWER ".0", NULL, 0, NULL, NULL },
|
||||
|
||||
/* outlet template definition
|
||||
* Caution: the index of the data start at 0, while the name is +1
|
||||
* ie outlet.1 => <OID>.0 */
|
||||
{ "outlet.%i.switchable", 0, 1, AR_OID_OUTLET_INDEX ".%i", "yes", SU_FLAG_STATIC | SU_OUTLET, NULL, NULL },
|
||||
{ "outlet.%i.id", 0, 1, NULL, "%i", SU_FLAG_STATIC | SU_FLAG_ABSENT | SU_FLAG_OK | SU_OUTLET, NULL, NULL },
|
||||
{ "outlet.%i.desc", ST_FLAG_RW | ST_FLAG_STRING, SU_INFOSIZE, AR_OID_OUTLET_NAME ".%i", NULL, SU_OUTLET, NULL, NULL },
|
||||
{ "outlet.%i.status", ST_FLAG_STRING, SU_INFOSIZE, AR_OID_OUTLET_STATUS ".%i", NULL, SU_FLAG_OK | SU_OUTLET, &outlet_status_info[0], NULL },
|
||||
{ "outlet.%i.current", 0, 0.001, AR_OID_OUTLET_CURRENT ".%i", NULL, SU_OUTLET, NULL, NULL },
|
||||
{ "outlet.%i.current.maximum", 0, 0.001, AR_OID_OUTLET_MAXCURRENT ".%i", NULL, SU_OUTLET, NULL, NULL },
|
||||
{ "outlet.%i.realpower", 0, 1.0, AR_OID_OUTLET_ACTIVEPOWER ".%i", NULL, SU_OUTLET, NULL, NULL },
|
||||
{ "outlet.%i.voltage", 0, 1.0, AR_OID_OUTLET_VOLTAGE ".%i", NULL, SU_OUTLET, NULL, NULL },
|
||||
{ "outlet.%i.powerfactor", 0, 0.01, AR_OID_OUTLET_POWERFACTOR ".%i", NULL, SU_OUTLET, NULL, NULL },
|
||||
{ "outlet.%i.power", 0, 1.0, AR_OID_OUTLET_APPARENTPOWER ".%i", NULL, SU_OUTLET, NULL, NULL },
|
||||
|
||||
/* FIXME:
|
||||
* - delay for startup/shutdown sequence
|
||||
* - support for Ambient page
|
||||
temperatureSensorCount" src="snmp:$sysoid.2.1.0
|
||||
ambient.temperature src="snmp:$sysoid.2.2.1.3.$indiceSensor => seems dumb!
|
||||
ambient.humidity src="snmp:$sysoid.2.4.1.3.$indiceSensor
|
||||
*/
|
||||
|
||||
/* instant commands. */
|
||||
/* Note that load.cycle might be replaced by / mapped on shutdown.reboot */
|
||||
/* no counterpart found!
|
||||
{ "outlet.load.off", 0, DO_OFF, AR_OID_OUTLET_STATUS ".0", NULL, SU_TYPE_CMD, NULL, NULL },
|
||||
{ "outlet.load.on", 0, DO_ON, AR_OID_OUTLET_STATUS ".0", NULL, SU_TYPE_CMD, NULL, NULL },
|
||||
{ "outlet.load.cycle", 0, DO_CYCLE, AR_OID_OUTLET_STATUS ".0", NULL, SU_TYPE_CMD, NULL, NULL }, */
|
||||
{ "outlet.%i.load.off", 0, DO_OFF, AR_OID_OUTLET_STATUS ".%i", NULL, SU_TYPE_CMD | SU_OUTLET, NULL, NULL },
|
||||
{ "outlet.%i.load.on", 0, DO_ON, AR_OID_OUTLET_STATUS ".%i", NULL, SU_TYPE_CMD | SU_OUTLET, NULL, NULL },
|
||||
{ "outlet.%i.load.cycle", 0, DO_CYCLE, AR_OID_OUTLET_STATUS ".%i", NULL, SU_TYPE_CMD | SU_OUTLET, NULL, NULL },
|
||||
|
||||
/* end of structure. */
|
||||
{ NULL, 0, 0, NULL, NULL, 0, NULL, NULL }
|
||||
};
|
||||
|
||||
mib2nut_info_t aphel_genesisII = { "aphel_genesisII", EATON_APHEL_MIB_VERSION, "", APHEL1_OID_MODEL_NAME, eaton_aphel_genesisII_mib };
|
||||
mib2nut_info_t aphel_revelation = { "aphel_revelation", EATON_APHEL_MIB_VERSION, "", APHEL2_OID_MODEL_NAME, eaton_aphel_revelation_mib };
|
||||
10
drivers/eaton-mib.h
Normal file
10
drivers/eaton-mib.h
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#ifndef EATON_MIB_H
|
||||
#define EATON_MIB_H
|
||||
|
||||
#include "main.h"
|
||||
#include "snmp-ups.h"
|
||||
|
||||
extern mib2nut_info_t aphel_genesisII;
|
||||
extern mib2nut_info_t aphel_revelation;
|
||||
|
||||
#endif /* EATON_MIB_H */
|
||||
368
drivers/etapro.c
Normal file
368
drivers/etapro.c
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
/* etapro.c - model specific routines for ETA UPS
|
||||
|
||||
Copyright (C) 2002 Marek Michalkiewicz <marekm@amelek.gda.pl>
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
/*
|
||||
This driver is for the ETA UPS (http://www.eta.com.pl/) with the
|
||||
"PRO" option (available at small added cost, highly recommended).
|
||||
All units (even without that option) should also work in "dumb"
|
||||
mode with the genericups driver (type 7 or 10), but in that mode
|
||||
shutdown only works when running on battery.
|
||||
|
||||
Tested with ETA mini+UPS 720 PRO. Thanks to ETA for help with
|
||||
protocol documentation, no free UPS though (but they still can
|
||||
send me another one if they like me ;-).
|
||||
|
||||
Shutdown should work even when on line, so this should help avoid
|
||||
power races (system remaining in halted or "ATX standby" state,
|
||||
requiring manual intervention). Delay from power off to power on
|
||||
can be set in software, currently hardcoded to 15 seconds.
|
||||
|
||||
Instant commands CMD_OFF and CMD_ON should work (not tested yet).
|
||||
Be careful with CMD_OFF - it turns off the load after one second.
|
||||
|
||||
Known issues:
|
||||
- larger units (>= 1000VA) have a 24V battery, so the battery
|
||||
voltage reported should be multiplied by 2 if the model
|
||||
string indicates such a larger unit.
|
||||
- load percentage is only measured when running on battery, and
|
||||
is reported as 0 when on line. This seems to be a hardware
|
||||
limitation of the UPS, so we can't do much about it...
|
||||
- UPS does not provide any remaining battery charge (or time at
|
||||
current load) information, but we should be able to estimate it
|
||||
based on battery voltage, load percentage and UPS model.
|
||||
- error handling not tested (we assume that the UPS is always
|
||||
correctly connected to the serial port).
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "serial.h"
|
||||
|
||||
#define DRIVER_NAME "ETA PRO driver"
|
||||
#define DRIVER_VERSION "0.04"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Marek Michalkiewicz <marekm@amelek.gda.pl>",
|
||||
DRV_STABLE,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static int
|
||||
etapro_get_response(const char *resp_type)
|
||||
{
|
||||
char tmp[256];
|
||||
char *cp;
|
||||
unsigned int n, val;
|
||||
|
||||
/* Read until a newline is found or there is no room in the buffer.
|
||||
Unlike ser_get_line(), don't discard the following characters
|
||||
because we have to handle multi-line responses. */
|
||||
n = 0;
|
||||
while (ser_get_char(upsfd, (unsigned char *)&tmp[n], 1, 0) == 1) {
|
||||
if (n >= sizeof(tmp) - 1 || tmp[n] == '\n')
|
||||
break;
|
||||
n++;
|
||||
}
|
||||
tmp[n] = '\0';
|
||||
if (n == 0) {
|
||||
upslogx(LOG_ERR, "no response from UPS");
|
||||
return -1;
|
||||
}
|
||||
/* Search for start of response (skip any echoed back command). */
|
||||
cp = strstr(tmp, resp_type);
|
||||
if (!cp || *cp == '\0' || cp[strlen(cp) - 1] != '\r') {
|
||||
upslogx(LOG_ERR, "bad response (%s)", tmp);
|
||||
return -1;
|
||||
}
|
||||
cp[strlen(cp) - 1] = '\0'; /* remove the CR */
|
||||
|
||||
switch (cp[1]) {
|
||||
/* Handle ASCII text responses directly here. */
|
||||
case 'R':
|
||||
dstate_setinfo("ups.mfr", "%s", cp + 2);
|
||||
return 0;
|
||||
case 'S':
|
||||
dstate_setinfo("ups.model", "%s", cp + 2);
|
||||
return 0;
|
||||
case 'T':
|
||||
dstate_setinfo("ups.mfr.date", "%s", cp + 2);
|
||||
return 0;
|
||||
}
|
||||
/* Handle all other responses as hexadecimal numbers. */
|
||||
val = 0;
|
||||
if (sscanf(cp + 2, "%x", &val) != 1) {
|
||||
upslogx(LOG_ERR, "bad response format (%s)", tmp);
|
||||
return -1;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
static void
|
||||
etapro_set_on_timer(int seconds)
|
||||
{
|
||||
int x;
|
||||
|
||||
if (seconds == 0) { /* cancel the running timer */
|
||||
ser_send(upsfd, "RS\r");
|
||||
x = etapro_get_response("SV");
|
||||
if (x == 0x30)
|
||||
return; /* OK */
|
||||
} else {
|
||||
if (seconds > 0x7fff) { /* minutes */
|
||||
seconds = (seconds + 59) / 60;
|
||||
if (seconds > 0x7fff)
|
||||
seconds = 0x7fff;
|
||||
printf("UPS on in %d minutes\n", seconds);
|
||||
seconds |= 0x8000;
|
||||
} else {
|
||||
printf("UPS on in %d seconds\n", seconds);
|
||||
}
|
||||
|
||||
ser_send(upsfd, "RN%04X\r", seconds);
|
||||
x = etapro_get_response("SV");
|
||||
if (x == 0x20)
|
||||
return; /* OK */
|
||||
}
|
||||
upslogx(LOG_ERR, "etapro_set_on_timer: error, status=0x%02x", x);
|
||||
}
|
||||
|
||||
static void
|
||||
etapro_set_off_timer(int seconds)
|
||||
{
|
||||
int x;
|
||||
|
||||
if (seconds == 0) { /* cancel the running timer */
|
||||
ser_send(upsfd, "RR\r");
|
||||
x = etapro_get_response("SV");
|
||||
if (x == 0x10)
|
||||
return; /* OK */
|
||||
} else {
|
||||
if (seconds > 0x7fff) { /* minutes */
|
||||
seconds /= 60;
|
||||
if (seconds > 0x7fff)
|
||||
seconds = 0x7fff;
|
||||
printf("UPS off in %d minutes\n", seconds);
|
||||
seconds |= 0x8000;
|
||||
} else {
|
||||
printf("UPS off in %d seconds\n", seconds);
|
||||
}
|
||||
|
||||
ser_send(upsfd, "RO%04X\r", seconds);
|
||||
x = etapro_get_response("SV");
|
||||
if (x == 0)
|
||||
return; /* OK */
|
||||
}
|
||||
upslogx(LOG_ERR, "etapro_set_off_timer: error, status=0x%02x", x);
|
||||
}
|
||||
|
||||
static int instcmd(const char *cmdname, const char *extra)
|
||||
{
|
||||
if (!strcasecmp(cmdname, "load.off")) {
|
||||
etapro_set_off_timer(1);
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
if (!strcasecmp(cmdname, "load.on")) {
|
||||
etapro_set_on_timer(1);
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
if (!strcasecmp(cmdname, "shutdown.return")) {
|
||||
upsdrv_shutdown();
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
}
|
||||
|
||||
void
|
||||
upsdrv_initinfo(void)
|
||||
{
|
||||
dstate_addcmd("load.off");
|
||||
dstate_addcmd("load.on");
|
||||
dstate_addcmd("shutdown.return");
|
||||
|
||||
/* First command after power on returns junk - ignore it. */
|
||||
ser_send(upsfd, "RI\r");
|
||||
sleep(1);
|
||||
|
||||
upsdrv_updateinfo();
|
||||
|
||||
upsh.instcmd = instcmd;
|
||||
}
|
||||
|
||||
void
|
||||
upsdrv_updateinfo(void)
|
||||
{
|
||||
char status[256];
|
||||
int x, flags;
|
||||
double utility, outvolt, battvolt, loadpct;
|
||||
|
||||
ser_flush_in(upsfd, "", nut_debug_level);
|
||||
ser_send(upsfd, "RI\r"); /* identify */
|
||||
|
||||
x = etapro_get_response("SR"); /* manufacturer */
|
||||
if (x < 0) {
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
x = etapro_get_response("SS"); /* model */
|
||||
if (x < 0) {
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
x = etapro_get_response("ST"); /* mfr date */
|
||||
if (x < 0) {
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
x = etapro_get_response("SU"); /* UPS ident */
|
||||
if (x < 0) {
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
|
||||
ser_send(upsfd, "RP\r"); /* read measurements */
|
||||
|
||||
x = etapro_get_response("SO"); /* status flags */
|
||||
if (x < 0) {
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
flags = x;
|
||||
|
||||
x = etapro_get_response("SG"); /* input voltage, 0xFF = 270V */
|
||||
if (x < 0) {
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
utility = (270.0 / 255) * x;
|
||||
|
||||
x = etapro_get_response("SH"); /* output voltage, 0xFF = 270V */
|
||||
if (x < 0) {
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
outvolt = (270.0 / 255) * x;
|
||||
|
||||
x = etapro_get_response("SI"); /* battery voltage, 0xFF = 14V */
|
||||
if (x < 0) {
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
|
||||
/* TODO: >= 1000VA models have a 24V battery (max 28V) - check
|
||||
the model string returned by the RI command. */
|
||||
battvolt = (14.0 / 255) * x;
|
||||
|
||||
x = etapro_get_response("SL"); /* load (on battery), 0xFF = 150% */
|
||||
if (x < 0) {
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
loadpct = (150.0 / 255) * x;
|
||||
|
||||
x = etapro_get_response("SN"); /* time running on battery */
|
||||
if (x < 0) {
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
/* This is the time how long the UPS has been running on battery
|
||||
(in seconds, reset to zero after power returns), but there
|
||||
seems to be no variable defined for this yet... */
|
||||
|
||||
status_init();
|
||||
|
||||
status[0] = '\0';
|
||||
if (!(flags & 0x02))
|
||||
status_set("OFF");
|
||||
else if (flags & 0x01)
|
||||
status_set("OL");
|
||||
else
|
||||
status_set("OB");
|
||||
|
||||
if (!(flags & 0x04))
|
||||
status_set("LB");
|
||||
|
||||
/* TODO bit 3: 1 = ok, 0 = fault */
|
||||
|
||||
if (flags & 0x10)
|
||||
status_set("BOOST");
|
||||
|
||||
if (loadpct > 100.0)
|
||||
status_set("OVER");
|
||||
|
||||
/* Battery voltage out of range (lower than LB, or too high). */
|
||||
if (flags & 0x20)
|
||||
status_set("RB");
|
||||
|
||||
/* TODO bit 6: 1 = charging, 0 = full */
|
||||
|
||||
status_commit();
|
||||
|
||||
dstate_setinfo("input.voltage", "%03.1f", utility);
|
||||
dstate_setinfo("output.voltage", "%03.1f", outvolt);
|
||||
dstate_setinfo("battery.voltage", "%02.2f", battvolt);
|
||||
dstate_setinfo("ups.load", "%03.1f", loadpct);
|
||||
|
||||
dstate_dataok();
|
||||
}
|
||||
|
||||
/* TODO: delays should be tunable, the UPS supports max 32767 minutes. */
|
||||
|
||||
/* Shutdown command to off delay in seconds. */
|
||||
#define SHUTDOWN_GRACE_TIME 10
|
||||
|
||||
/* Shutdown to return delay in seconds. */
|
||||
#define SHUTDOWN_TO_RETURN_TIME 15
|
||||
|
||||
void
|
||||
upsdrv_shutdown(void)
|
||||
{
|
||||
etapro_set_on_timer(SHUTDOWN_GRACE_TIME + SHUTDOWN_TO_RETURN_TIME);
|
||||
etapro_set_off_timer(SHUTDOWN_GRACE_TIME);
|
||||
}
|
||||
|
||||
void
|
||||
upsdrv_help(void)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
upsdrv_makevartable(void)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
upsdrv_initups(void)
|
||||
{
|
||||
upsfd = ser_open(device_path);
|
||||
ser_set_speed(upsfd, device_path, B1200);
|
||||
|
||||
ser_set_dtr(upsfd, 0);
|
||||
ser_set_rts(upsfd, 1);
|
||||
}
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
ser_close(upsfd, device_path);
|
||||
}
|
||||
201
drivers/everups.c
Normal file
201
drivers/everups.c
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
/* everups.c - support for Ever UPS models
|
||||
|
||||
Copyright (C) 2001 Bartek Szady <bszx@bszxdomain.edu.eu.org>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "serial.h"
|
||||
|
||||
#define DRIVER_NAME "Ever UPS driver"
|
||||
#define DRIVER_VERSION "0.03"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Bartek Szady <bszx@bszxdomain.edu.eu.org>",
|
||||
DRV_STABLE,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static unsigned char upstype = 0;
|
||||
|
||||
static void init_serial(void)
|
||||
{
|
||||
ser_set_dtr(upsfd, 0);
|
||||
ser_set_rts(upsfd, 0);
|
||||
}
|
||||
|
||||
static int Code(int tries)
|
||||
{
|
||||
unsigned char cRecv;
|
||||
do {
|
||||
ser_send_char(upsfd, 208);
|
||||
ser_get_char(upsfd, &cRecv, 3, 0);
|
||||
if (cRecv==208)
|
||||
return 1;
|
||||
} while (--tries>0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int InitUpsType(void)
|
||||
{
|
||||
if (Code(1)) {
|
||||
ser_send_char(upsfd, 173);
|
||||
ser_get_char(upsfd, &upstype, 3, 0);
|
||||
return 1;
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *GetTypeUpsName(void)
|
||||
{
|
||||
switch(upstype)
|
||||
{
|
||||
case 67: return "NET 500-DPC";
|
||||
case 68: return "NET 700-DPC";
|
||||
case 69: return "NET 1000-DPC";
|
||||
case 70: return "NET 1400-DPC";
|
||||
case 71: return "NET 2200-DPC";
|
||||
case 73: return "NET 700-DPC (new)";
|
||||
case 74: return "NET 1000-DPC (new)";
|
||||
case 75: return "NET 1400-DPC (new)";
|
||||
case 76: return "NET 500-DPC (new)";
|
||||
case 81: return "AP 450-PRO";
|
||||
case 82: return "AP 650-PRO";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
void upsdrv_initinfo(void)
|
||||
{
|
||||
dstate_setinfo("ups.mfr", "Ever");
|
||||
dstate_setinfo("ups.model", "%s", GetTypeUpsName());
|
||||
}
|
||||
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
int battery=0,standby=0;
|
||||
unsigned char recBuf[2];
|
||||
unsigned long acuV;
|
||||
unsigned long lineV;
|
||||
double fVal;
|
||||
|
||||
if (!Code(2)) {
|
||||
upslog_with_errno(LOG_INFO, "Code failed");
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
/*Line status*/
|
||||
ser_send_char(upsfd, 175);
|
||||
ser_get_char(upsfd, recBuf, 3, 0);
|
||||
if ((recBuf[0] & 1) !=0)
|
||||
standby=1;
|
||||
else
|
||||
battery=(recBuf[0] &4) !=0;
|
||||
if (Code(1)) { /*Accumulator voltage value*/
|
||||
ser_send_char(upsfd, 189);
|
||||
ser_get_char(upsfd, recBuf, 3, 0);
|
||||
acuV=((unsigned long)recBuf[0])*150;
|
||||
acuV/=255;
|
||||
} else {
|
||||
upslog_with_errno(LOG_INFO, "Code failed");
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
if (Code(1)) { /*Line voltage*/
|
||||
ser_send_char(upsfd, 245);
|
||||
ser_get_buf_len(upsfd, recBuf, 2, 3, 0);
|
||||
if ( upstype > 72 && upstype < 77)
|
||||
lineV=(recBuf[0]*100+recBuf[1]*25600)/352;
|
||||
else
|
||||
lineV=(recBuf[0]*100+recBuf[1]*25600)/372;
|
||||
} else {
|
||||
upslog_with_errno(LOG_INFO, "Code failed");
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
|
||||
status_init();
|
||||
|
||||
if (battery && acuV<105)
|
||||
status_set("LB"); /* low battery */
|
||||
|
||||
if (battery)
|
||||
status_set("OB"); /* on battery */
|
||||
else
|
||||
status_set("OL"); /* on line */
|
||||
|
||||
status_commit();
|
||||
|
||||
dstate_setinfo("input.voltage", "%03ld", lineV);
|
||||
dstate_setinfo("battery.voltage", "%03.2f", (double)acuV /10.0);
|
||||
|
||||
fVal=((double)acuV-95.0)*100.0;
|
||||
if (standby)
|
||||
fVal/=(135.5-95.0);
|
||||
else
|
||||
fVal/=(124.5-95.0);
|
||||
if (fVal>100)
|
||||
fVal=100;
|
||||
else if (fVal<0)
|
||||
fVal=0;
|
||||
|
||||
dstate_setinfo("battery.charge", "%03.1f", fVal);
|
||||
|
||||
dstate_dataok();
|
||||
}
|
||||
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
if (!Code(2)) {
|
||||
upslog_with_errno(LOG_INFO, "Code failed");
|
||||
return;
|
||||
}
|
||||
ser_send_char(upsfd, 28);
|
||||
ser_send_char(upsfd, 1); /* 1.28 sec */
|
||||
if (!Code(1)) {
|
||||
upslog_with_errno(LOG_INFO, "Code failed");
|
||||
return;
|
||||
}
|
||||
ser_send_char(upsfd, 13);
|
||||
ser_send_char(upsfd, 8);
|
||||
}
|
||||
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
}
|
||||
|
||||
/* list flags and values that you want to receive via -x */
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
}
|
||||
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
upsfd = ser_open(device_path);
|
||||
ser_set_speed(upsfd, device_path, B300);
|
||||
|
||||
init_serial();
|
||||
InitUpsType();
|
||||
}
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
ser_close(upsfd, device_path);
|
||||
}
|
||||
75
drivers/explore-hid.c
Normal file
75
drivers/explore-hid.c
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/* explore-hid.c - this is a "stub" subdriver used to collect data
|
||||
* about HID UPS systems that are not yet supported.
|
||||
*
|
||||
* This subdriver will match any UPS, but only if the "-x explore" option
|
||||
* is given.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "usbhid-ups.h"
|
||||
#include "explore-hid.h"
|
||||
|
||||
#define EXPLORE_HID_VERSION "EXPLORE HID 0.1"
|
||||
|
||||
static usage_tables_t explore_utab[] = {
|
||||
hid_usage_lkp,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* Data lookup table (HID <-> NUT) */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
static hid_info_t explore_hid2nut[] =
|
||||
{
|
||||
/* end of structure. */
|
||||
{ NULL, 0, 0, NULL, NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
static char *explore_format_model(HIDDevice_t *hd) {
|
||||
return hd->Product;
|
||||
}
|
||||
|
||||
static char *explore_format_mfr(HIDDevice_t *hd) {
|
||||
return hd->Vendor;
|
||||
}
|
||||
|
||||
static char *explore_format_serial(HIDDevice_t *hd) {
|
||||
return hd->Serial;
|
||||
}
|
||||
|
||||
/* this function allows the subdriver to "claim" a device: return 1 if
|
||||
* the device is supported by this subdriver, else 0. */
|
||||
static int explore_claim(HIDDevice_t *hd) {
|
||||
if (testvar("explore")) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
subdriver_t explore_subdriver = {
|
||||
EXPLORE_HID_VERSION,
|
||||
explore_claim,
|
||||
explore_utab,
|
||||
explore_hid2nut,
|
||||
explore_format_model,
|
||||
explore_format_mfr,
|
||||
explore_format_serial,
|
||||
};
|
||||
28
drivers/explore-hid.h
Normal file
28
drivers/explore-hid.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/* explore-hid.h - this is a "stub" subdriver used to collect data
|
||||
* about HID UPS systems that are not yet supported.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef EXPLORE_HID_H
|
||||
#define EXPLORE_HID_H
|
||||
|
||||
#include "usbhid-ups.h"
|
||||
|
||||
extern subdriver_t explore_subdriver;
|
||||
|
||||
#endif /* EXPLORE_HID_H */
|
||||
409
drivers/gamatronic.c
Normal file
409
drivers/gamatronic.c
Normal file
|
|
@ -0,0 +1,409 @@
|
|||
/* gamatronic.c
|
||||
*
|
||||
* SEC UPS Driver ported to the new NUT API for Gamatronic UPS Usage.
|
||||
*
|
||||
* Copyright (C)
|
||||
* 2001 John Marley <John.Marley@alcatel.com.au>
|
||||
* 2002 Jules Taplin <jules@netsitepro.co.uk>
|
||||
* 2002 Eric Lawson <elawson@inficad.com>
|
||||
* 2005 Arnaud Quette <arnaud.quette@gmail.com>
|
||||
* 2005 Nadav Moskovitch <blutz@walla.com / http://www.gamatronic.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "serial.h"
|
||||
#include "gamatronic.h"
|
||||
|
||||
#define DRIVER_NAME "Gamatronic UPS driver"
|
||||
#define DRIVER_VERSION "0.02"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"John Marley <John.Marley@alcatel.com.au>\n" \
|
||||
"Jules Taplin <jules@netsitepro.co.uk>\n" \
|
||||
"Eric Lawson <elawson@inficad.com>\n" \
|
||||
"Arnaud Quette <arnaud.quette@gmail.com>\n" \
|
||||
"Nadav Moskovitch <blutz@walla.com / http://www.gamatronic.com>",
|
||||
DRV_STABLE,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
#define ENDCHAR '\r'
|
||||
#define IGNCHARS ""
|
||||
#define SER_WAIT_SEC 1 /* allow 3.0 sec for ser_get calls */
|
||||
#define SER_WAIT_USEC 0
|
||||
|
||||
int sec_upsrecv (char *buf)
|
||||
{
|
||||
|
||||
char lenbuf[4];
|
||||
int ret;
|
||||
|
||||
|
||||
ser_get_line(upsfd, buf, 140, ENDCHAR, IGNCHARS,SER_WAIT_SEC, SER_WAIT_USEC);
|
||||
if (buf[0] == SEC_MSG_STARTCHAR){
|
||||
switch (buf[1]){
|
||||
case SEC_NAK:
|
||||
return(-1);
|
||||
case SEC_ACK:
|
||||
return(0);
|
||||
case SEC_DATAMSG:
|
||||
strncpy(lenbuf,buf+2,3);
|
||||
ret = atoi(lenbuf);
|
||||
if (ret > 0){
|
||||
strcpy(buf,buf+5);
|
||||
return(ret);}
|
||||
else return (-2);
|
||||
default:
|
||||
return(-2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{ return (-2); }
|
||||
}
|
||||
|
||||
int sec_cmd(const char mode, const char *command, char *msgbuf, int *buflen)
|
||||
{
|
||||
char msg[140];
|
||||
int ret;
|
||||
|
||||
memset(msg, 0, sizeof(msg));
|
||||
|
||||
/* create the message string */
|
||||
if (*buflen > 0) {
|
||||
snprintf(msg, sizeof(msg), "%c%c%03d%s%s", SEC_MSG_STARTCHAR,
|
||||
mode, (*buflen)+3, command, msgbuf);
|
||||
}
|
||||
else {
|
||||
snprintf(msg, sizeof(msg), "%c%c003%s", SEC_MSG_STARTCHAR,
|
||||
mode, command);
|
||||
}
|
||||
upsdebugx(1, "PC-->UPS: \"%s\"",msg);
|
||||
ret = ser_send(upsfd, "%s", msg);
|
||||
|
||||
upsdebugx(1, " send returned: %d",ret);
|
||||
|
||||
if (ret == -1) return -1;
|
||||
|
||||
ret = sec_upsrecv(msg);
|
||||
|
||||
|
||||
if (ret < 0) return -1;
|
||||
|
||||
if (ret >= 0) {
|
||||
strncpy(msgbuf, msg, ret);
|
||||
upsdebugx(1, "UPS<--PC: \"%s\"",msg);
|
||||
}
|
||||
/* *(msgbuf+ret) = '\0';*/
|
||||
|
||||
*buflen = ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void addquery(char *cmd, int field, int varnum, int pollflag)
|
||||
{
|
||||
int q;
|
||||
|
||||
for (q=0; q<SEC_QUERYLIST_LEN; q++) {
|
||||
if (sec_querylist[q].command == NULL) {
|
||||
/* command has not been recorded yet */
|
||||
sec_querylist[q].command = cmd;
|
||||
sec_querylist[q].pollflag = pollflag;
|
||||
upsdebugx(1, " Query %d is %s",q,cmd);
|
||||
}
|
||||
if (sec_querylist[q].command == cmd) {
|
||||
sec_querylist[q].varnum[field-1] = varnum;
|
||||
upsdebugx(1, " Querying varnum %d",varnum);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sec_setinfo(int varnum, char *value)
|
||||
{
|
||||
|
||||
if (*sec_varlist[varnum].setcmd){/*Not empty*/
|
||||
|
||||
if (sec_varlist[varnum].flags == FLAG_STRING) {
|
||||
dstate_setinfo(sec_varlist[varnum].setcmd,"%s", value);
|
||||
}
|
||||
else if (sec_varlist[varnum].unit == 1) {
|
||||
dstate_setinfo(sec_varlist[varnum].setcmd,"%s", value);
|
||||
}
|
||||
|
||||
else if (sec_varlist[varnum].flags == FLAG_MULTI) {
|
||||
if (atoi(value) < 0) {
|
||||
dstate_setinfo(sec_varlist[varnum].setcmd,"0");
|
||||
}
|
||||
else
|
||||
{dstate_setinfo(sec_varlist[varnum].setcmd,"%d", atoi(value) * sec_varlist[varnum].unit);
|
||||
}
|
||||
}
|
||||
else {
|
||||
dstate_setinfo(sec_varlist[varnum].setcmd,"%.1f", atof(value) / sec_varlist[varnum].unit);}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
void update_pseudovars( void )
|
||||
{
|
||||
|
||||
status_init();
|
||||
|
||||
if(strcmp(sec_varlist[9].value,"1")== 0) {
|
||||
status_set("OFF");
|
||||
}
|
||||
if(strcmp(sec_varlist[76].value,"0")== 0) {
|
||||
status_set("OL");
|
||||
}
|
||||
if(strcmp(sec_varlist[76].value,"1")== 0) {
|
||||
status_set("OB");
|
||||
}
|
||||
if(strcmp(sec_varlist[76].value,"2")== 0) {
|
||||
status_set("BYPASS");
|
||||
}
|
||||
if(strcmp(sec_varlist[76].value,"3")== 0) {
|
||||
status_set("TRIM");
|
||||
}
|
||||
if(strcmp(sec_varlist[76].value,"4")== 0) {
|
||||
status_set("BOOST");
|
||||
}
|
||||
if(strcmp(sec_varlist[10].value,"1")== 0) {
|
||||
status_set("OVER");
|
||||
}
|
||||
if(strcmp(sec_varlist[22].value,"1")== 0) {
|
||||
status_set("LB");
|
||||
}
|
||||
|
||||
if(strcmp(sec_varlist[19].value,"2")== 0) {
|
||||
status_set("RB");
|
||||
}
|
||||
|
||||
status_commit();
|
||||
|
||||
|
||||
}
|
||||
|
||||
void sec_poll ( int pollflag ) {
|
||||
|
||||
int msglen,f,q;
|
||||
char retbuf[140],*n,*r;
|
||||
|
||||
|
||||
for (q=0; q<SEC_QUERYLIST_LEN; q++) {
|
||||
if (sec_querylist[q].command == NULL) break;
|
||||
if (sec_querylist[q].pollflag != pollflag) continue;
|
||||
msglen = 0;
|
||||
sec_cmd(SEC_POLLCMD, sec_querylist[q].command, retbuf, &msglen);
|
||||
r = retbuf;
|
||||
*(r+msglen) = '\0';
|
||||
for (f=0; f<SEC_MAXFIELDS; f++) {
|
||||
n = strchr(r, ',');
|
||||
if (n != NULL) *n = '\0';
|
||||
if (sqv(q,f) > 0) {
|
||||
|
||||
if (strcmp(sec_varlist[sqv(q,f)].value, r) != 0 ) {
|
||||
|
||||
snprintf(sec_varlist[sqv(q,f)].value,
|
||||
sizeof(sec_varlist[sqv(q,f)].value), "%s", r);
|
||||
|
||||
sec_setinfo(sqv(q,f), r);
|
||||
}
|
||||
|
||||
/* If SEC VAR is alarm and its on, add it to the alarm property */
|
||||
|
||||
if (sec_varlist[sqv(q,f)].flags & FLAG_ALARM && strcmp(r,"1")== 0) {
|
||||
alarm_set(sec_varlist[sqv(q,f)].name); }
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (n == NULL) break;
|
||||
r = n+1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void upsdrv_initinfo(void)
|
||||
{
|
||||
|
||||
int msglen, e, v;
|
||||
char *a,*p,avail_list[300];
|
||||
|
||||
/* find out which variables/commands this UPS supports */
|
||||
msglen = 0;
|
||||
sec_cmd(SEC_POLLCMD, SEC_AVAILP1, avail_list, &msglen);
|
||||
p = avail_list + msglen;
|
||||
if (p != avail_list) *p++ = ',';
|
||||
msglen = 0;
|
||||
sec_cmd(SEC_POLLCMD, SEC_AVAILP2, p, &msglen);
|
||||
*(p+msglen) = '\0';
|
||||
|
||||
|
||||
if (strlen(avail_list) == 0){
|
||||
fatalx(EXIT_FAILURE, "No available variables found!");}
|
||||
a = avail_list;
|
||||
e = 0;
|
||||
while ((p = strtok(a, ",")) != NULL) {
|
||||
a = NULL;
|
||||
v = atoi(p);
|
||||
/* don't bother adding a write-only variable */
|
||||
if (sec_varlist[v].flags == FLAG_WONLY) continue;
|
||||
addquery(sec_varlist[v].cmd, sec_varlist[v].field, v, sec_varlist[v].poll);
|
||||
}
|
||||
|
||||
/* poll one time values */
|
||||
|
||||
sec_poll(FLAG_POLLONCE);
|
||||
|
||||
printf("UPS: %s %s\n", dstate_getinfo("ups.mfr"), dstate_getinfo("ups.model"));
|
||||
|
||||
|
||||
}
|
||||
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
|
||||
|
||||
alarm_init();
|
||||
/* poll status values values */
|
||||
sec_poll(FLAG_POLL);
|
||||
alarm_commit();
|
||||
update_pseudovars();
|
||||
dstate_dataok();
|
||||
|
||||
}
|
||||
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
|
||||
int msg_len;
|
||||
|
||||
msg_len = 2;
|
||||
sec_cmd (SEC_SETCMD,SEC_SHUTDOWN,"-1",&msg_len);
|
||||
msg_len = 1 ;
|
||||
sec_cmd (SEC_SETCMD,SEC_AUTORESTART,"1",&msg_len);
|
||||
msg_len = 1;
|
||||
sec_cmd (SEC_SETCMD, SEC_SHUTTYPE,"2",&msg_len);
|
||||
msg_len = 1;
|
||||
sec_cmd (SEC_SETCMD,SEC_SHUTDOWN,"5",&msg_len);
|
||||
}
|
||||
|
||||
/*
|
||||
static int instcmd(const char *cmdname, const char *extra)
|
||||
{
|
||||
if (!strcasecmp(cmdname, "test.battery.stop")) {
|
||||
ser_send_buf(upsfd, ...);
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
}
|
||||
*/
|
||||
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
}
|
||||
|
||||
/* list flags and values that you want to receive via -x */
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
/* allow '-x xyzzy' */
|
||||
/* addvar(VAR_FLAG, "xyzzy", "Enable xyzzy mode"); */
|
||||
|
||||
/* allow '-x foo=<some value>' */
|
||||
/* addvar(VAR_VALUE, "foo", "Override foo setting"); */
|
||||
}
|
||||
|
||||
void setup_serial(const char *port)
|
||||
{
|
||||
char temp[140];
|
||||
int i,ret;
|
||||
|
||||
|
||||
/* Detect the ups baudrate */
|
||||
|
||||
|
||||
for (i=0; i<5; i++) {
|
||||
|
||||
ser_set_speed(upsfd, device_path,baud_rates[i].rate);
|
||||
ret = ser_send(upsfd, "^P003MAN");
|
||||
ret = sec_upsrecv(temp);
|
||||
if (ret >= -1) break;
|
||||
|
||||
}
|
||||
if (i == 5) {
|
||||
printf("Can't talk to UPS on port %s!\n",port);
|
||||
printf("Check the cabling and portname and try again\n");
|
||||
printf("Please note that this driver only support UPS Models with SEC Protorol\n");
|
||||
ser_close(upsfd, device_path);
|
||||
exit (1);
|
||||
}
|
||||
printf("Connected to UPS on %s baudrate: %d\n",port, baud_rates[i].name);
|
||||
}
|
||||
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
upsfd = ser_open(device_path);
|
||||
setup_serial(device_path);
|
||||
/* upsfd = ser_open(device_path); */
|
||||
/* ser_set_speed(upsfd, device_path, B1200); */
|
||||
|
||||
/* probe ups type */
|
||||
|
||||
/* to get variables and flags from the command line, use this:
|
||||
*
|
||||
* first populate with upsdrv_buildvartable above, then...
|
||||
*
|
||||
* set flag foo : /bin/driver -x foo
|
||||
* set variable 'cable' to '1234' : /bin/driver -x cable=1234
|
||||
*
|
||||
* to test flag foo in your code:
|
||||
*
|
||||
* if (testvar("foo"))
|
||||
* do_something();
|
||||
*
|
||||
* to show the value of cable:
|
||||
*
|
||||
* if ((cable == getval("cable")))
|
||||
* printf("cable is set to %s\n", cable);
|
||||
* else
|
||||
* printf("cable is not set!\n");
|
||||
*
|
||||
* don't use NULL pointers - test the return result first!
|
||||
*/
|
||||
|
||||
/* the upsh handlers can't be done here, as they get initialized
|
||||
* shortly after upsdrv_initups returns to main.
|
||||
*/
|
||||
|
||||
|
||||
}
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
/* free(dynamic_mem); */
|
||||
ser_close(upsfd, device_path);
|
||||
}
|
||||
201
drivers/gamatronic.h
Normal file
201
drivers/gamatronic.h
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
/* gamatronic.h
|
||||
*
|
||||
* SEC UPS Driver ported to the new NUT API for Gamatronic UPS Usage.
|
||||
*
|
||||
* Copyright (C)
|
||||
* 2001 John Marley <John.Marley@alcatel.com.au>
|
||||
* 2002 Jules Taplin <jules@netsitepro.co.uk>
|
||||
* 2002 Eric Lawson <elawson@inficad.com>
|
||||
* 2005 Arnaud Quette <http://arnaud.quette.free.fr/contact.html>
|
||||
* 2005 Nadav Moskovitch <blutz@walla.com / http://www.gamatronic.com>
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#define SEC_MSG_STARTCHAR '^'
|
||||
#define SEC_POLLCMD 'P'
|
||||
#define SEC_SETCMD 'S'
|
||||
#define SEC_DATAMSG 'D'
|
||||
#define SEC_UPSMSG '*'
|
||||
#define SEC_ACK '1'
|
||||
#define SEC_NAK '0'
|
||||
|
||||
/* commands */
|
||||
#define SEC_AVAILP1 "AP1" /* Part1 of available variables */
|
||||
#define SEC_AVAILP2 "AP2" /* Part1 of available variables */
|
||||
#define SEC_AUTORESTART "ATR" /* Enable/disable auto restart */
|
||||
#define SEC_MFR "MAN" /* UPS Manufacturer */
|
||||
#define SEC_MOD "MOD" /* UPS Model */
|
||||
#define SEC_NOMINAL "NOM" /* Nominal Values */
|
||||
#define SEC_SHUTDOWN "PSD" /* Shutdown after delay/cancel */
|
||||
#define SEC_REBOOT "RWD" /* Reboot with duration/cancel */
|
||||
#define SEC_SHUTTYPE "SDA" /* Shutdown Type */
|
||||
#define SEC_BATTSTAT "ST1" /* Battery Status */
|
||||
#define SEC_INPUTSTAT "ST2" /* Input Status */
|
||||
#define SEC_OUTPUTSTAT "ST3" /* Output Status */
|
||||
#define SEC_BYPASSSTAT "ST4" /* Bypass Status */
|
||||
#define SEC_ALARMSTAT "ST5" /* UPS Alarms */
|
||||
#define SEC_STARTDELAY "STD" /* Startup after delay */
|
||||
#define SEC_TESTRESULT "STR" /* Test Results */
|
||||
#define SEC_TEST "TST" /* UPS Test/abort */
|
||||
#define SEC_BAUDRATE "UBR" /* UPS Baud Rate */
|
||||
#define SEC_UPSID "UID" /* UPS Identifier */
|
||||
#define SEC_VERSION "VER" /* UPS Software Version */
|
||||
|
||||
#define FLAG_STRING 1
|
||||
#define FLAG_RW 2
|
||||
#define FLAG_WONLY 3 /* Dont waste time on reading commands that are read only */
|
||||
#define FLAG_ALARM 4 /* If the value of a var with this flag equals to 1, then its name added to alarms list */
|
||||
#define FLAG_MULTI 5 /* Multiple UNIT value instead of Dividing It */
|
||||
|
||||
#define FLAG_POLL 0 /* For commands that polled normaly */
|
||||
#define FLAG_POLLONCE 1 /* For commands that only polled once */
|
||||
/* Some baud rates for setup_serial() */
|
||||
struct baud_rate_t {
|
||||
int rate;
|
||||
int name;
|
||||
} baud_rates[] = {
|
||||
{ B1200, 1200 },
|
||||
{ B2400, 2400 },
|
||||
{ B4800, 4800 },
|
||||
{ B9600, 9600 },
|
||||
{ B19200, 19200 },
|
||||
};
|
||||
|
||||
#define SEC_NUMVARS 89
|
||||
#define SEC_MAX_VARSIZE 65
|
||||
|
||||
/* macro for checking whether a variable is supported */
|
||||
|
||||
struct sec_varlist_t {
|
||||
char *setcmd; /* INFO_x define from shared.h */
|
||||
char *name; /* Human readable text (also in shared-tables.h) */
|
||||
int unit; /* Variable should be divided by this */
|
||||
char *cmd; /* Command to send to pool/set variable */
|
||||
int field; /* Which returned field variable corresponsd to */
|
||||
int size; /* string length/integer max/enum count */
|
||||
int poll; /* poll flag */
|
||||
int flags; /* Flags for addinfo() */
|
||||
char value[SEC_MAX_VARSIZE];
|
||||
} sec_varlist[] = {
|
||||
{ "", "", 0, "", 0, 0, 0, 0 },
|
||||
/*setcmd name unit cmd field size poll flags */
|
||||
{ "", "Awaiting Power ", 1, SEC_ALARMSTAT, 13, 2, 0, FLAG_ALARM},
|
||||
{ "", "Bypass Bad ", 1, SEC_ALARMSTAT, 5, 2, 0, FLAG_ALARM},
|
||||
{ "", "Charger Failure ", 1, SEC_ALARMSTAT, 8, 2, 0, FLAG_ALARM},
|
||||
{ "", "Fan Failure ", 1, SEC_ALARMSTAT, 10, 2, 0, FLAG_ALARM},
|
||||
{ "", "Fuse Failure ", 1, SEC_ALARMSTAT, 11, 2, 0, FLAG_ALARM},
|
||||
{ "", "General Fault ", 1, SEC_ALARMSTAT, 12, 2, 0, FLAG_ALARM},
|
||||
{ "", "Input Bad ", 1, SEC_ALARMSTAT, 2, 2, 0, FLAG_ALARM},
|
||||
{ "", "Output Bad ", 1, SEC_ALARMSTAT, 3, 2, 0, FLAG_ALARM},
|
||||
{ "", "Output Off ", 1, SEC_ALARMSTAT, 6, 2, 0, FLAG_ALARM},
|
||||
{ "", "Overload ", 1, SEC_ALARMSTAT, 4, 2, 0, FLAG_ALARM},
|
||||
{ "", "Shutdown Imminent ", 1, SEC_ALARMSTAT, 15, 2, 0, FLAG_ALARM},
|
||||
{ "", "Shutdown Pending ", 1, SEC_ALARMSTAT, 14, 2, 0, FLAG_ALARM},
|
||||
{ "", "System Off ", 1, SEC_ALARMSTAT, 9, 2, 0, FLAG_ALARM},
|
||||
{ "", "Temperature ", 1, SEC_ALARMSTAT, 1, 2, 0, FLAG_ALARM},
|
||||
{ "", "UPS Shutdown ", 1, SEC_ALARMSTAT, 7, 2, 0, FLAG_ALARM},
|
||||
{ "", "Audible Alarm", 1, SEC_NOMINAL, 8, 4, FLAG_POLLONCE, FLAG_RW},
|
||||
{ "", "Auto Restart", 1, SEC_AUTORESTART, 1, 2, FLAG_POLLONCE, FLAG_RW},
|
||||
{ "", "Battery Charge", 1, SEC_BATTSTAT, 3, 4, 0, 0},
|
||||
{ "", "Battery Condition", 1, SEC_BATTSTAT, 1, 3, 0, 0},
|
||||
{ "battery.current", "Battery Current", 10, SEC_BATTSTAT, 8, 9999, 0, 0 },
|
||||
{ "battery.date", "Battery Installed", 1, SEC_NOMINAL, 11, 8, FLAG_POLLONCE, FLAG_STRING},
|
||||
{ "", "Battery Status", 1, SEC_BATTSTAT, 2, 3, 0, 0 },
|
||||
{ "battery.temperature", "Battery Temperature", 1, SEC_BATTSTAT, 9, 99, 0, 0 },
|
||||
{ "battery.voltage", "Battery Voltage", 10, SEC_BATTSTAT, 7, 9999, 0, 0 },
|
||||
{ "", "Bypass Current 1", 10, SEC_BYPASSSTAT, 4, 9999, 0, 0 },
|
||||
{ "", "Bypass Current 2", 10, SEC_BYPASSSTAT, 7, 9999, 0, 0 },
|
||||
{ "", "Bypass Current 3", 10, SEC_BYPASSSTAT, 10, 9999, 0, 0 },
|
||||
{ "", "Bypass Frequency", 10, SEC_BYPASSSTAT, 1, 999, 0, 0 },
|
||||
{ "", "Bypass Num Lines", 1, SEC_BYPASSSTAT, 2, 9, 0, 0 },
|
||||
{ "", "Bypass Power 1", 1, SEC_BYPASSSTAT, 5, 99999, 0, 0 },
|
||||
{ "", "Bypass Power 2", 1, SEC_BYPASSSTAT, 8, 99999, 0, 0 },
|
||||
{ "", "Bypass Power 3", 1, SEC_BYPASSSTAT, 11, 99999, 0, 0 },
|
||||
{ "", "Bypass Voltage 1", 10, SEC_BYPASSSTAT, 3, 9999, 0, 0 },
|
||||
{ "", "Bypass Voltage 2", 10, SEC_BYPASSSTAT, 6, 9999, 0, 0 },
|
||||
{ "", "Bypass Voltage 3", 10, SEC_BYPASSSTAT, 9, 9999, 0, 0 },
|
||||
{ "battery.charge", "Estimated Charge", 1, SEC_BATTSTAT, 6, 999, 0, 0 },
|
||||
{ "battery.runtime.low", "Estimated Minutes", 60, SEC_BATTSTAT, 5, 999, 0, FLAG_MULTI },
|
||||
{ "input.transfer.high", "High Volt Xfer Pt", 1, SEC_NOMINAL, 10, 999, FLAG_POLLONCE, FLAG_STRING},
|
||||
{ "ups.id", "Identification", 1, SEC_UPSID, 1, 64, FLAG_POLLONCE, FLAG_STRING},
|
||||
{ "", "Input Current 1", 10, SEC_INPUTSTAT, 5, 9999, 0, 0 },
|
||||
{ "", "Input Current 2", 10, SEC_INPUTSTAT, 9, 9999, 0, 0 },
|
||||
{ "", "Input Current 3", 10, SEC_INPUTSTAT, 13, 9999, 0, 0 },
|
||||
{ "input.frequency", "Input Frequency 1", 10, SEC_INPUTSTAT, 3, 999, 0, 0 },
|
||||
{ "", "Input Frequency 2", 10, SEC_INPUTSTAT, 7, 999, 0, 0 },
|
||||
{ "", "Input Frequency 3", 10, SEC_INPUTSTAT, 11, 999, 0, 0 },
|
||||
{ "", "Input Line Bads", 1, SEC_INPUTSTAT, 1, 999, 0, 0 },
|
||||
{ "", "Input Num Lines", 1, SEC_INPUTSTAT, 2, 9, 0, 0 },
|
||||
{ "", "Input Power 1", 1, SEC_INPUTSTAT, 6, 99999, 0, 0 },
|
||||
{ "", "Input Power 2", 1, SEC_INPUTSTAT, 10, 99999, 0, 0 },
|
||||
{ "", "Input Power 3", 1, SEC_INPUTSTAT, 14, 99999, 0, 0 },
|
||||
{ "input.voltage", "Input Voltage 1", 10, SEC_INPUTSTAT, 4, 9999, 0, 0 },
|
||||
{ "", "Input Voltage 2", 10, SEC_INPUTSTAT, 8, 9999, 0, 0 },
|
||||
{ "", "Input Voltage 3", 10, SEC_INPUTSTAT, 12, 9999, 0, 0 },
|
||||
{ "input.transfer.low", "Low Volt Xfer Pt", 1, SEC_NOMINAL, 9, 999, FLAG_POLLONCE, FLAG_STRING},
|
||||
{ "ups.mfr", "Manufacturer", 1, SEC_MFR, 1, 32, FLAG_POLLONCE, FLAG_STRING},
|
||||
{ "ups.model", "Model", 1, SEC_MOD, 1, 64, FLAG_POLLONCE, FLAG_STRING},
|
||||
{ "", "Nominal Battery Life", 1, SEC_NOMINAL, 12, 99999, FLAG_POLLONCE, FLAG_STRING},
|
||||
{ "", "Nominal Input Frequency", 10, SEC_NOMINAL, 2, 999, FLAG_POLLONCE, FLAG_RW},
|
||||
{ "input.voltage.nominal", "Nominal Input Voltage", 1, SEC_NOMINAL, 1, 999, FLAG_POLLONCE, FLAG_STRING},
|
||||
{ "", "Nominal Low Battery Time", 1, SEC_NOMINAL, 7, 99, FLAG_POLLONCE, FLAG_STRING},
|
||||
{ "", "Nominal Output Frequency", 10, SEC_NOMINAL, 4, 999, FLAG_POLLONCE, FLAG_RW},
|
||||
{ "", "Nominal Output Power", 1, SEC_NOMINAL, 6, 99999, FLAG_POLLONCE, FLAG_STRING},
|
||||
{ "", "Nominal Output Voltage", 1, SEC_NOMINAL, 3, 999, FLAG_POLLONCE, FLAG_STRING},
|
||||
{ "ups.power.nominal", "Nominal VA Rating", 1, SEC_NOMINAL, 5, 99999, FLAG_POLLONCE, FLAG_STRING},
|
||||
{ "output.current", "Output Current 1", 10, SEC_OUTPUTSTAT, 5, 9999, 0, 0 },
|
||||
{ "", "Output Current 2", 10, SEC_OUTPUTSTAT, 9, 9999, 0, 0 },
|
||||
{ "", "Output Current 3", 10, SEC_OUTPUTSTAT, 13, 9999, 0, 0 },
|
||||
{ "output.frequency", "Output Frequency", 10, SEC_OUTPUTSTAT, 2, 999, 0, 0 },
|
||||
{ "ups.load", "Output Load 1", 1, SEC_OUTPUTSTAT, 7, 999, 0, 0 },
|
||||
{ "", "Output Load 2", 1, SEC_OUTPUTSTAT, 11, 999, 0, 0 },
|
||||
{ "", "Output Load 3", 1, SEC_OUTPUTSTAT, 15, 999, 0, 0 },
|
||||
{ "", "Output Num Lines", 1, SEC_OUTPUTSTAT, 3, 9, 0, 0 },
|
||||
{ "", "Output Power 1", 1, SEC_OUTPUTSTAT, 6, 99999, 0, 0 },
|
||||
{ "", "Output Power 2", 1, SEC_OUTPUTSTAT, 10, 99999, 0, 0 },
|
||||
{ "", "Output Power 3", 1, SEC_OUTPUTSTAT, 14, 99999, 0, 0 },
|
||||
{ "", "Output Source", 1, SEC_OUTPUTSTAT, 1, 6, 0, 0},
|
||||
{ "output.voltage", "Output Voltage 1", 10, SEC_OUTPUTSTAT, 4, 9999, 0, 0 },
|
||||
{ "", "Output Voltage 2", 10, SEC_OUTPUTSTAT, 8, 9999, 0, 0 },
|
||||
{ "", "Output Voltage 3", 10, SEC_OUTPUTSTAT, 12, 9999, 0, 0 },
|
||||
{ "", "Reboot With Duration", 1, SEC_REBOOT, 1, 9999999, FLAG_POLLONCE, FLAG_WONLY},
|
||||
{ "battery.runtime", "Seconds on Battery", 1, SEC_BATTSTAT, 4, 99999, 0, 0 },
|
||||
{ "", "Shutdown Type", 1, SEC_SHUTTYPE, 1, 2, FLAG_POLLONCE, FLAG_RW},
|
||||
{ "ups.delay.shutdown", "Shutdown After Delay", 1, SEC_STARTDELAY, 1, 9999999, FLAG_POLLONCE, FLAG_WONLY},
|
||||
{ "ups.firmware", "Software Version", 1, SEC_VERSION, 1, 32, FLAG_POLLONCE, FLAG_STRING},
|
||||
{ "", "Startup After Delay", 1, SEC_STARTDELAY, 1, 9999999, FLAG_POLLONCE, FLAG_WONLY},
|
||||
{ "", "Test Results Detail", 1, SEC_TESTRESULT, 2, 64, FLAG_POLLONCE, FLAG_STRING},
|
||||
{ "", "Test Results Summary", 1, SEC_TESTRESULT, 1, 6, FLAG_POLLONCE, 0},
|
||||
{ "", "Test Type", 1, SEC_TEST, 1, 5, FLAG_POLLONCE, FLAG_WONLY},
|
||||
{ "", "Baud Rate", 1, SEC_BAUDRATE, 1, 19200, FLAG_POLLONCE, FLAG_RW},
|
||||
};
|
||||
|
||||
|
||||
/* a type for the supported variables */
|
||||
#define SEC_QUERYLIST_LEN 17
|
||||
#define SEC_MAXFIELDS 16
|
||||
#define SEC_POLL 1
|
||||
#define SEC_POLLONCE 0
|
||||
|
||||
struct sec_querylist_t {
|
||||
char *command; /* sec command */
|
||||
int varnum[SEC_MAXFIELDS]; /* sec variable number for each field */
|
||||
int pollflag;
|
||||
} sec_querylist[SEC_QUERYLIST_LEN];
|
||||
|
||||
#define sqv(a,b) sec_querylist[a].varnum[b]
|
||||
|
||||
338
drivers/genericups.c
Normal file
338
drivers/genericups.c
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
/* genericups.c - support for generic contact-closure UPS models
|
||||
|
||||
Copyright (C) 1999 Russell Kroll <rkroll@exploits.org>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include "main.h"
|
||||
#include "serial.h"
|
||||
#include "genericups.h"
|
||||
|
||||
#define DRIVER_NAME "Generic contact-closure UPS driver"
|
||||
#define DRIVER_VERSION "1.36"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Russell Kroll <rkroll@exploits.org>",
|
||||
DRV_STABLE,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static int upstype = -1;
|
||||
|
||||
static void parse_output_signals(const char *value, int *line)
|
||||
{
|
||||
/* parse signals the serial port can output */
|
||||
|
||||
*line = 0;
|
||||
|
||||
if (strstr(value, "DTR") && !strstr(value, "-DTR")) {
|
||||
*line |= TIOCM_DTR;
|
||||
}
|
||||
|
||||
if (strstr(value, "RTS") && !strstr(value, "-RTS")) {
|
||||
*line |= TIOCM_RTS;
|
||||
}
|
||||
|
||||
if (strstr(value, "ST")) {
|
||||
*line |= TIOCM_ST;
|
||||
}
|
||||
|
||||
if (strstr(value, "CTS")) {
|
||||
fatalx(EXIT_FAILURE, "Can't override output with CTS (not an output)");
|
||||
}
|
||||
|
||||
if (strstr(value, "DCD")) {
|
||||
fatalx(EXIT_FAILURE, "Can't override output with DCD (not an output)");
|
||||
}
|
||||
|
||||
if (strstr(value, "RNG")) {
|
||||
fatalx(EXIT_FAILURE, "Can't override output with RNG (not an output)");
|
||||
}
|
||||
|
||||
if (strstr(value, "DSR")) {
|
||||
fatalx(EXIT_FAILURE, "Can't override output with DSR (not an output)");
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_input_signals(const char *value, int *line, int *val)
|
||||
{
|
||||
/* parse signals the serial port can input */
|
||||
|
||||
*line = 0;
|
||||
*val = 0;
|
||||
|
||||
if (strstr(value, "CTS")) {
|
||||
*line |= TIOCM_CTS;
|
||||
|
||||
if (!strstr(value, "-CTS")) {
|
||||
*val |= TIOCM_CTS;
|
||||
}
|
||||
}
|
||||
|
||||
if (strstr(value, "DCD")) {
|
||||
*line |= TIOCM_CD;
|
||||
|
||||
if (!strstr(value, "-DCD")) {
|
||||
*val |= TIOCM_CD;
|
||||
}
|
||||
}
|
||||
|
||||
if (strstr(value, "RNG")) {
|
||||
*line |= TIOCM_RNG;
|
||||
|
||||
if (!strstr(value, "-RNG")) {
|
||||
*val |= TIOCM_RNG;
|
||||
}
|
||||
}
|
||||
|
||||
if (strstr(value, "DSR")) {
|
||||
*line |= TIOCM_DSR;
|
||||
|
||||
if (!strstr(value, "-DSR")) {
|
||||
*val |= TIOCM_DSR;
|
||||
}
|
||||
}
|
||||
|
||||
if (strstr(value, "DTR")) {
|
||||
fatalx(EXIT_FAILURE, "Can't override input with DTR (not an input)");
|
||||
}
|
||||
|
||||
if (strstr(value, "RTS")) {
|
||||
fatalx(EXIT_FAILURE, "Can't override input with RTS (not an input)");
|
||||
}
|
||||
|
||||
if (strstr(value, "ST")) {
|
||||
fatalx(EXIT_FAILURE, "Can't override input with ST (not an input)");
|
||||
}
|
||||
}
|
||||
|
||||
void upsdrv_initinfo(void)
|
||||
{
|
||||
char *v;
|
||||
|
||||
/* setup the basics */
|
||||
|
||||
dstate_setinfo("ups.mfr", "%s", ((v = getval("mfr")) != NULL) ? v : upstab[upstype].mfr);
|
||||
dstate_setinfo("ups.model", "%s", ((v = getval("model")) != NULL) ? v : upstab[upstype].model);
|
||||
|
||||
if ((v = getval("serial")) != NULL) {
|
||||
dstate_setinfo("ups.serial", "%s", v);
|
||||
}
|
||||
|
||||
/*
|
||||
User wants to override the input signal definitions. See also upsdrv_initups().
|
||||
*/
|
||||
if ((v = getval("OL")) != NULL) {
|
||||
parse_input_signals(v, &upstab[upstype].line_ol, &upstab[upstype].val_ol);
|
||||
upsdebugx(2, "parse_input_signals: OL overriden with %s\n", v);
|
||||
}
|
||||
|
||||
if ((v = getval("LB")) != NULL) {
|
||||
parse_input_signals(v, &upstab[upstype].line_bl, &upstab[upstype].val_bl);
|
||||
upsdebugx(2, "parse_input_signals: LB overriden with %s\n", v);
|
||||
}
|
||||
}
|
||||
|
||||
/* normal idle loop - keep up with the current state of the UPS */
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
int flags, ol, bl, ret;
|
||||
|
||||
ret = ioctl(upsfd, TIOCMGET, &flags);
|
||||
|
||||
if (ret != 0) {
|
||||
upslog_with_errno(LOG_INFO, "ioctl failed");
|
||||
ser_comm_fail("Status read failed");
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
|
||||
ol = ((flags & upstab[upstype].line_ol) == upstab[upstype].val_ol);
|
||||
bl = ((flags & upstab[upstype].line_bl) == upstab[upstype].val_bl);
|
||||
|
||||
status_init();
|
||||
|
||||
if (bl) {
|
||||
status_set("LB"); /* low battery */
|
||||
}
|
||||
|
||||
if (ol) {
|
||||
status_set("OL"); /* on line */
|
||||
} else {
|
||||
status_set("OB"); /* on battery */
|
||||
}
|
||||
|
||||
status_commit();
|
||||
|
||||
upsdebugx(5, "ups.status: %s %s\n", ol ? "OL" : "OB", bl ? "BL" : "");
|
||||
|
||||
ser_comm_good();
|
||||
dstate_dataok();
|
||||
}
|
||||
|
||||
/* show all possible UPS types */
|
||||
static void listtypes(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
printf("Valid UPS types:\n\n");
|
||||
|
||||
for (i = 0; upstab[i].mfr != NULL; i++) {
|
||||
printf("%i: %s\n", i, upstab[i].desc);
|
||||
}
|
||||
}
|
||||
|
||||
/* set the flags for this UPS type */
|
||||
static void set_ups_type(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!getval("upstype")) {
|
||||
fatalx(EXIT_FAILURE, "No upstype set - see help text / man page!");
|
||||
}
|
||||
|
||||
upstype = atoi(getval("upstype"));
|
||||
|
||||
for (i = 0; upstab[i].mfr != NULL; i++) {
|
||||
|
||||
if (upstype == i) {
|
||||
upslogx(LOG_INFO, "UPS type: %s\n", upstab[i].desc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
listtypes();
|
||||
|
||||
fatalx(EXIT_FAILURE, "\nFatal error: unknown UPS type number");
|
||||
}
|
||||
|
||||
/* power down the attached load immediately */
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
int flags, ret;
|
||||
|
||||
if (upstype == -1) {
|
||||
fatalx(EXIT_FAILURE, "No upstype set - see help text / man page!");
|
||||
}
|
||||
|
||||
flags = upstab[upstype].line_sd;
|
||||
|
||||
if (flags == -1) {
|
||||
fatalx(EXIT_FAILURE, "No shutdown command defined for this model!");
|
||||
}
|
||||
|
||||
if (flags == TIOCM_ST) {
|
||||
|
||||
#ifndef HAVE_TCSENDBREAK
|
||||
fatalx(EXIT_FAILURE, "Need to send a BREAK, but don't have tcsendbreak!");
|
||||
#endif
|
||||
|
||||
ret = tcsendbreak(upsfd, 4901);
|
||||
|
||||
if (ret != 0) {
|
||||
fatal_with_errno(EXIT_FAILURE, "tcsendbreak");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ret = ioctl(upsfd, TIOCMSET, &flags);
|
||||
|
||||
if (ret != 0) {
|
||||
fatal_with_errno(EXIT_FAILURE, "ioctl TIOCMSET");
|
||||
}
|
||||
|
||||
if (getval("sdtime")) {
|
||||
int sdtime;
|
||||
|
||||
sdtime = strtol(getval("sdtime"), (char **) NULL, 10);
|
||||
|
||||
upslogx(LOG_INFO, "Holding shutdown signal for %d seconds...\n",
|
||||
sdtime);
|
||||
|
||||
sleep(sdtime);
|
||||
}
|
||||
}
|
||||
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
listtypes();
|
||||
}
|
||||
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
addvar(VAR_VALUE, "upstype", "Set UPS type (required)");
|
||||
addvar(VAR_VALUE, "mfr", "Override manufacturer name");
|
||||
addvar(VAR_VALUE, "model", "Override model name");
|
||||
addvar(VAR_VALUE, "serial", "Specify the serial number");
|
||||
addvar(VAR_VALUE, "CP", "Override cable power setting");
|
||||
addvar(VAR_VALUE, "OL", "Override on line signal");
|
||||
addvar(VAR_VALUE, "LB", "Override low battery signal");
|
||||
addvar(VAR_VALUE, "SD", "Override shutdown setting");
|
||||
addvar(VAR_VALUE, "sdtime", "Hold time for shutdown value (seconds)");
|
||||
}
|
||||
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
struct termios tio;
|
||||
char *v;
|
||||
|
||||
set_ups_type();
|
||||
|
||||
upsfd = ser_open(device_path);
|
||||
|
||||
if (tcgetattr(upsfd, &tio)) {
|
||||
fatal_with_errno(EXIT_FAILURE, "tcgetattr");
|
||||
}
|
||||
|
||||
/* don't hang up on last close */
|
||||
tio.c_cflag &= ~HUPCL;
|
||||
|
||||
if (tcsetattr(upsfd, TCSANOW, &tio)) {
|
||||
fatal_with_errno(EXIT_FAILURE, "tcsetattr");
|
||||
}
|
||||
|
||||
/*
|
||||
See if the user wants to override the output signal definitions
|
||||
this must be done here, since we might go to upsdrv_shutdown()
|
||||
immediately. Input signal definition override is handled in
|
||||
upsdrv_initinfo()
|
||||
*/
|
||||
if ((v = getval("CP")) != NULL) {
|
||||
parse_output_signals(v, &upstab[upstype].line_norm);
|
||||
upsdebugx(2, "parse_output_signals: CP overriden with %s\n", v);
|
||||
}
|
||||
|
||||
if ((v = getval("SD")) != NULL) {
|
||||
parse_output_signals(v, &upstab[upstype].line_sd);
|
||||
upsdebugx(2, "parse_output_signals: SD overriden with %s\n", v);
|
||||
}
|
||||
|
||||
if (ioctl(upsfd, TIOCMSET, &upstab[upstype].line_norm)) {
|
||||
fatal_with_errno(EXIT_FAILURE, "ioctl TIOCMSET");
|
||||
}
|
||||
}
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
ser_close(upsfd, device_path);
|
||||
}
|
||||
|
||||
272
drivers/genericups.h
Normal file
272
drivers/genericups.h
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
/* genericups.h - contact closure UPS line status definitions
|
||||
|
||||
Copyright (C) 1999, 2000 Russell Kroll <rkroll@exploits.org>
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
struct {
|
||||
const char *mfr; /* value for INFO_MFR */
|
||||
const char *model; /* value for INFO_MODEL */
|
||||
const char *desc; /* used in -h listing */
|
||||
int line_norm;
|
||||
int line_ol, val_ol;
|
||||
int line_bl, val_bl;
|
||||
int line_sd;
|
||||
} upstab[] =
|
||||
{
|
||||
/* Type 0 */
|
||||
{ "UPSONIC",
|
||||
"LAN Saver 600",
|
||||
"UPSONIC LAN Saver 600",
|
||||
TIOCM_DTR | TIOCM_RTS, /* cable power: DTR + RTS */
|
||||
TIOCM_CTS, 0, /* online: CTS off */
|
||||
TIOCM_CD, TIOCM_CD, /* low battery: CD on */
|
||||
TIOCM_RTS /* shutdown: RTS */
|
||||
},
|
||||
|
||||
/* Type 1 */
|
||||
{ "APC",
|
||||
"Back-UPS",
|
||||
"APC Back-UPS (940-0095A/C cable)",
|
||||
TIOCM_DTR, /* cable power: DTR */
|
||||
TIOCM_RNG, 0, /* online: RNG off */
|
||||
TIOCM_CD, TIOCM_CD, /* low battery: CD on */
|
||||
TIOCM_RTS /* shutdown: RTS */
|
||||
},
|
||||
|
||||
/* Type 2 */
|
||||
{ "APC",
|
||||
"Back-UPS",
|
||||
"APC Back-UPS (940-0020B/C cable)",
|
||||
TIOCM_RTS, /* cable power: RTS */
|
||||
TIOCM_CTS, 0, /* online: CTS off */
|
||||
TIOCM_CD, TIOCM_CD, /* low battery: CD on */
|
||||
TIOCM_DTR|TIOCM_RTS /* shutdown: DTR + RTS */
|
||||
},
|
||||
|
||||
/* Type 3 */
|
||||
{ "PowerTech",
|
||||
"Comp1000",
|
||||
"PowerTech Comp1000 with DTR as cable power",
|
||||
TIOCM_DTR, /* cable power: DTR */
|
||||
TIOCM_CTS, 0, /* online: CTS off */
|
||||
TIOCM_CD, TIOCM_CD, /* low battery: CD on */
|
||||
TIOCM_DTR | TIOCM_RTS /* shutdown: DTR + RTS */
|
||||
},
|
||||
|
||||
/* Type 4 */
|
||||
{ "Generic",
|
||||
"Generic RUPS model",
|
||||
"Generic RUPS model",
|
||||
TIOCM_RTS, /* cable power: RTS */
|
||||
TIOCM_CTS, TIOCM_CTS, /* online: CTS on */
|
||||
TIOCM_CD, 0, /* low battery: CD off */
|
||||
0 /* shutdown: none */
|
||||
},
|
||||
|
||||
/* Type 5 */
|
||||
{ "TrippLite",
|
||||
"Internet Office Series",
|
||||
"Tripp Lite UPS with Lan2.2 interface (black 73-0844 cable)",
|
||||
TIOCM_DTR, /* cable power: DTR */
|
||||
TIOCM_CTS, TIOCM_CTS, /* online: CTS on */
|
||||
TIOCM_CD, 0, /* low battery: CD off */
|
||||
TIOCM_DTR | TIOCM_RTS /* shutdown: DTR + RTS */
|
||||
},
|
||||
|
||||
/* Type 6 */
|
||||
{ "Best",
|
||||
"Patriot",
|
||||
"Best Patriot (INT51 cable)",
|
||||
TIOCM_DTR, /* cable power: DTR */
|
||||
TIOCM_CTS, TIOCM_CTS, /* online: CTS on */
|
||||
TIOCM_CD, 0, /* low battery: CD off */
|
||||
TIOCM_RTS /* shutdown: set RTS */
|
||||
},
|
||||
|
||||
/* Type 7 */
|
||||
{ "CyberPower",
|
||||
"Power99",
|
||||
"CyberPower Power99",
|
||||
TIOCM_RTS, /* cable power: RTS */
|
||||
TIOCM_CTS, TIOCM_CTS, /* online: CTS on */
|
||||
TIOCM_CD, 0, /* low battery: CD off */
|
||||
TIOCM_DTR /* shutdown: set DTR */
|
||||
},
|
||||
|
||||
/* Type 8 */
|
||||
{ "Nitram",
|
||||
"Elite UPS",
|
||||
"Nitram Elite 500",
|
||||
TIOCM_DTR, /* cable power: DTR */
|
||||
TIOCM_CTS, TIOCM_CTS, /* online: CTS on */
|
||||
TIOCM_CD, 0, /* low battery: CD off */
|
||||
-1 /* shutdown: unknown */
|
||||
},
|
||||
|
||||
/* Type 9 */
|
||||
{ "APC",
|
||||
"Back-UPS",
|
||||
"APC Back-UPS (940-0023A cable)",
|
||||
0, /* cable power: none */
|
||||
TIOCM_CD, 0, /* online: CD off */
|
||||
TIOCM_CTS, TIOCM_CTS, /* low battery: CTS on */
|
||||
TIOCM_RTS /* shutdown: RTS */
|
||||
},
|
||||
|
||||
/* Type 10 (duplicate from 7) */
|
||||
{ "Victron",
|
||||
"Lite",
|
||||
"Victron Lite (crack cable)",
|
||||
TIOCM_RTS, /* cable power: RTS */
|
||||
TIOCM_CTS, TIOCM_CTS, /* online: CTS on */
|
||||
TIOCM_CD, 0, /* low battery: CD off */
|
||||
TIOCM_DTR /* shutdown: DTR */
|
||||
},
|
||||
|
||||
/* Type 11 */
|
||||
{ "Powerware",
|
||||
"3115",
|
||||
"Powerware 3115",
|
||||
TIOCM_DTR, /* cable power: DTR */
|
||||
TIOCM_CTS, 0, /* online: CTS off */
|
||||
TIOCM_CD, 0, /* low battery: CD off */
|
||||
TIOCM_ST /* shutdown: ST */
|
||||
},
|
||||
|
||||
/* Type 12 */
|
||||
{ "APC",
|
||||
"Back-UPS Office",
|
||||
"APC Back-UPS Office (940-0119A cable)",
|
||||
TIOCM_RTS, /* cable power: RTS */
|
||||
TIOCM_CTS, 0, /* online: CTS off */
|
||||
TIOCM_CD, TIOCM_CD, /* low battery: CD on */
|
||||
TIOCM_DTR /* shutdown: raise DTR */
|
||||
},
|
||||
|
||||
/* Type 13 */
|
||||
{ "RPT",
|
||||
"Repoteck",
|
||||
"Repoteck RPT-800A, RPT-162A",
|
||||
TIOCM_DTR | TIOCM_RTS, /* cable power: DTR + RTS */
|
||||
TIOCM_CD, TIOCM_CD, /* On-line : DCD on */
|
||||
TIOCM_CTS, 0, /* Battery low: CTS off */
|
||||
TIOCM_ST /* shutdown: TX BREA */
|
||||
},
|
||||
|
||||
/* Type 14 */
|
||||
{ "Online",
|
||||
"P250, P500, P750, P1250",
|
||||
"Online P-series",
|
||||
TIOCM_DTR, /* cable power: DTR */
|
||||
TIOCM_CD, TIOCM_CD, /* online: CD on */
|
||||
TIOCM_CTS, 0, /* low battery: CTS off */
|
||||
TIOCM_RTS /* shutdown: raise RTS */
|
||||
},
|
||||
|
||||
/* Type 15 */
|
||||
{ "Powerware",
|
||||
"5119",
|
||||
"Powerware 5119",
|
||||
TIOCM_DTR, /* cable power: DTR */
|
||||
TIOCM_CTS, TIOCM_CTS, /* online: CTS on */
|
||||
TIOCM_CD, 0, /* low battery: CD off */
|
||||
TIOCM_ST /* shutdown: ST (break) */
|
||||
},
|
||||
|
||||
/* Type 16 */
|
||||
{ "Nitram",
|
||||
"Elite UPS",
|
||||
"Nitram Elite 2002",
|
||||
TIOCM_DTR | TIOCM_RTS, /* cable power: DTR + RTS */
|
||||
TIOCM_CTS, TIOCM_CTS, /* online: CTS on */
|
||||
TIOCM_CD, 0, /* low battery: CD off */
|
||||
-1 /* shutdown: unknown */
|
||||
},
|
||||
|
||||
/* Type 17 (duplicate from 8) */
|
||||
{ "PowerKinetics",
|
||||
"9001",
|
||||
"PowerKinetics 9001",
|
||||
TIOCM_DTR, /* cable power: DTR */
|
||||
TIOCM_CTS, TIOCM_CTS, /* online: CTS on */
|
||||
TIOCM_CD, 0, /* low battery: CD off */
|
||||
-1 /* shutdown: unknown */
|
||||
},
|
||||
|
||||
/* Type 18 */
|
||||
{ "TrippLite",
|
||||
"Omni 450LAN",
|
||||
"TrippLite UPS with Martin's cabling",
|
||||
TIOCM_DTR, /* cable power: DTR */
|
||||
TIOCM_CTS, TIOCM_CTS, /* online: CTS on */
|
||||
TIOCM_CD, TIOCM_CD, /* low battery: CAR on */
|
||||
-1 /* shutdown: none */
|
||||
},
|
||||
|
||||
/* Type 19 (duplicate from 6) */
|
||||
{ "Fideltronik",
|
||||
"Ares Series",
|
||||
"Fideltronik Ares Series",
|
||||
TIOCM_DTR, /* cable power: DTR */
|
||||
TIOCM_CTS, TIOCM_CTS, /* online: CTS on */
|
||||
TIOCM_CD, 0, /* low battery: DCD off */
|
||||
TIOCM_RTS /* shutdown: set RTS */
|
||||
},
|
||||
|
||||
/* Type 20 */
|
||||
/* docs/cables/powerware.txt */
|
||||
{ "Powerware",
|
||||
"5119 RM",
|
||||
"Powerware 5119 RM",
|
||||
TIOCM_DTR, /* cable power: DTR */
|
||||
TIOCM_CTS, 0, /* online: CTS off */
|
||||
TIOCM_CD, TIOCM_CD, /* low battery: CD on */
|
||||
TIOCM_ST /* shutdown: ST (break) */
|
||||
},
|
||||
|
||||
/* Type 21 */
|
||||
/* http://lists.exploits.org/upsdev/Oct2004/00004.html */
|
||||
{ "Generic",
|
||||
"Generic RUPS 2000",
|
||||
"Generic RUPS 2000 (Megatec M2501 cable)",
|
||||
TIOCM_RTS, /* cable power: RTS */
|
||||
TIOCM_CTS, TIOCM_CTS, /* online: CTS on */
|
||||
TIOCM_CD, 0, /* low battery: CD off */
|
||||
TIOCM_RTS | TIOCM_DTR /* shutdown: RTS+DTR */
|
||||
},
|
||||
|
||||
/* Type 22 (duplicate from 7)*/
|
||||
{ "Gamatronic Electronic Industries",
|
||||
"Generic Alarm UPS",
|
||||
"Gamatronic UPSs with alarm interface",
|
||||
TIOCM_RTS, /* cable power: RTS */
|
||||
TIOCM_CTS, TIOCM_CTS, /* online: CTS on */
|
||||
TIOCM_CD, 0, /* low battery: CD off */
|
||||
TIOCM_DTR /* shutdown: DTR */
|
||||
},
|
||||
|
||||
/* add any new entries directly above this line */
|
||||
|
||||
{ NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
0,
|
||||
0, 0,
|
||||
0, 0,
|
||||
0
|
||||
}
|
||||
};
|
||||
618
drivers/hidparser.c
Normal file
618
drivers/hidparser.c
Normal file
|
|
@ -0,0 +1,618 @@
|
|||
/*
|
||||
* hidparser.c: HID Parser
|
||||
*
|
||||
* This file is part of the MGE UPS SYSTEMS HID Parser
|
||||
*
|
||||
* Copyright (C)
|
||||
* 1998-2003 MGE UPS SYSTEMS, Luc Descotils
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "hidparser.h"
|
||||
#include "nut_stdint.h" /* for int8_t, int16_t, int32_t */
|
||||
|
||||
/* to be implemented for DEBUG purpose */
|
||||
/* previously: #define ERROR(x) if(x) __asm { int 3 }; */
|
||||
#define ERROR(x)
|
||||
|
||||
static const uint8_t ItemSize[4] = { 0, 1, 2, 4 };
|
||||
|
||||
/*
|
||||
* HIDParser struct
|
||||
* -------------------------------------------------------------------------- */
|
||||
typedef struct {
|
||||
const unsigned char *ReportDesc; /* Report Descriptor */
|
||||
int ReportDescSize; /* Size of Report Descriptor */
|
||||
|
||||
uint16_t Pos; /* Store current pos in descriptor */
|
||||
uint8_t Item; /* Store current Item */
|
||||
long Value; /* Store current Value */
|
||||
|
||||
HIDData_t Data; /* Store current environment */
|
||||
|
||||
uint8_t OffsetTab[MAX_REPORT][4]; /* Store ID, Type, offset & timestamp of report */
|
||||
uint8_t ReportCount; /* Store Report Count */
|
||||
uint8_t Count; /* Store local report count */
|
||||
|
||||
uint16_t UPage; /* Global UPage */
|
||||
HIDNode_t UsageTab[USAGE_TAB_SIZE]; /* Usage stack */
|
||||
uint8_t UsageSize; /* Design number of usage used */
|
||||
} HIDParser_t;
|
||||
|
||||
/* return 1 + the position of the leftmost "1" bit of an int, or 0 if
|
||||
none. */
|
||||
static inline unsigned int hibit(unsigned int x)
|
||||
{
|
||||
unsigned int res = 0;
|
||||
|
||||
while (x > 0xff) {
|
||||
x >>= 8;
|
||||
res += 8;
|
||||
}
|
||||
|
||||
while (x) {
|
||||
x >>= 1;
|
||||
res += 1;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Note: The USB HID specification states that Local items do not
|
||||
carry over to the next Main item (version 1.11, section
|
||||
6.2.2.8). Therefore the local state must be reset after each main
|
||||
item. In particular, any unused usages on the Usage tabs must be
|
||||
discarded and must not carry over to the next Main item. Some APC
|
||||
equipment actually sends multiple redundant "usage" commands for a
|
||||
single control, so resetting the local state is important. */
|
||||
/* Also note: UsageTab[0] is used as the usage of the next control,
|
||||
even if UsageSize=0. Therefore, this must be initialized */
|
||||
static void ResetLocalState(HIDParser_t* pParser)
|
||||
{
|
||||
pParser->UsageSize = 0;
|
||||
memset(pParser->UsageTab, 0, sizeof(pParser->UsageTab));
|
||||
}
|
||||
|
||||
/*
|
||||
* GetReportOffset
|
||||
*
|
||||
* Return pointer on current offset value for Report designed by
|
||||
* ReportID/ReportType
|
||||
* -------------------------------------------------------------------------- */
|
||||
static uint8_t *GetReportOffset(HIDParser_t* pParser, const uint8_t ReportID, const uint8_t ReportType)
|
||||
{
|
||||
int Pos;
|
||||
|
||||
for (Pos = 0; Pos < MAX_REPORT; Pos++) {
|
||||
|
||||
if (pParser->OffsetTab[Pos][0] == 0) {
|
||||
pParser->OffsetTab[Pos][0] = ReportID;
|
||||
pParser->OffsetTab[Pos][1] = ReportType;
|
||||
pParser->OffsetTab[Pos][2] = 0;
|
||||
}
|
||||
|
||||
if (pParser->OffsetTab[Pos][0] != ReportID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pParser->OffsetTab[Pos][1] != ReportType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return &pParser->OffsetTab[Pos][2];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* FormatValue(long Value, uint8_t Size)
|
||||
* Format Value to fit with long format with respect of negative values
|
||||
* -------------------------------------------------------------------------- */
|
||||
static long FormatValue(long Value, uint8_t Size)
|
||||
{
|
||||
switch(Size)
|
||||
{
|
||||
case 1:
|
||||
return (long)(int8_t)Value;
|
||||
case 2:
|
||||
return (long)(int16_t)Value;
|
||||
case 4:
|
||||
return (long)(int32_t)Value;
|
||||
default:
|
||||
return Value;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* HIDParse(HIDParser_t* pParser, HIDData_t *pData)
|
||||
*
|
||||
* Analyse Report descriptor stored in HIDParser struct and store local and
|
||||
* global context.
|
||||
* Return in pData the last object found.
|
||||
* Return -1 when there is no other Item to parse, 1 if a new object was found
|
||||
* or 0 if a continuation of a previous object was found.
|
||||
* -------------------------------------------------------------------------- */
|
||||
static int HIDParse(HIDParser_t *pParser, HIDData_t *pData)
|
||||
{
|
||||
int Found = -1;
|
||||
|
||||
while ((Found < 0) && (pParser->Pos < pParser->ReportDescSize)) {
|
||||
/* Get new pParser->Item if current pParser->Count is empty */
|
||||
if (pParser->Count == 0) {
|
||||
pParser->Item = pParser->ReportDesc[pParser->Pos++];
|
||||
pParser->Value = 0;
|
||||
#if WORDS_BIGENDIAN
|
||||
{
|
||||
int i;
|
||||
unsigned long valTmp = 0;
|
||||
|
||||
for (i = 0; i < ItemSize[pParser->Item & SIZE_MASK]; i++) {
|
||||
memcpy(&valTmp, &pParser->ReportDesc[(pParser->Pos)+i], 1);
|
||||
pParser->Value += valTmp >> ((3-i)*8);
|
||||
valTmp = 0;
|
||||
}
|
||||
}
|
||||
#else
|
||||
memcpy(&pParser->Value, &pParser->ReportDesc[pParser->Pos], ItemSize[pParser->Item & SIZE_MASK]);
|
||||
#endif
|
||||
/* Pos on next item */
|
||||
pParser->Pos += ItemSize[pParser->Item & SIZE_MASK];
|
||||
}
|
||||
|
||||
switch (pParser->Item & ITEM_MASK)
|
||||
{
|
||||
case ITEM_UPAGE:
|
||||
/* Copy UPage in Usage stack */
|
||||
pParser->UPage=(uint16_t)pParser->Value;
|
||||
break;
|
||||
|
||||
case ITEM_USAGE:
|
||||
/* Copy global or local UPage if any, in Usage stack */
|
||||
if ((pParser->Item & SIZE_MASK) > 2) {
|
||||
pParser->UsageTab[pParser->UsageSize] = pParser->Value;
|
||||
} else {
|
||||
pParser->UsageTab[pParser->UsageSize] = (pParser->UPage << 16) | (pParser->Value & 0xFFFF);
|
||||
}
|
||||
|
||||
/* Increment Usage stack size */
|
||||
pParser->UsageSize++;
|
||||
break;
|
||||
|
||||
case ITEM_COLLECTION:
|
||||
/* Get UPage/Usage from UsageTab and store them in pParser->Data.Path */
|
||||
pParser->Data.Path.Node[pParser->Data.Path.Size] = pParser->UsageTab[0];
|
||||
pParser->Data.Path.Size++;
|
||||
|
||||
/* Unstack UPage/Usage from UsageTab (never remove the last) */
|
||||
if (pParser->UsageSize > 0) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < pParser->UsageSize; i++) {
|
||||
pParser->UsageTab[i] = pParser->UsageTab[i+1];
|
||||
}
|
||||
|
||||
/* Remove Usage */
|
||||
pParser->UsageSize--;
|
||||
}
|
||||
|
||||
/* Get Index if any */
|
||||
if (pParser->Value >= 0x80) {
|
||||
pParser->Data.Path.Node[pParser->Data.Path.Size] = 0x00ff0000 | (pParser->Value & 0x7F);
|
||||
pParser->Data.Path.Size++;
|
||||
}
|
||||
|
||||
ResetLocalState(pParser);
|
||||
break;
|
||||
|
||||
case ITEM_END_COLLECTION :
|
||||
pParser->Data.Path.Size--;
|
||||
|
||||
/* Remove Index if any */
|
||||
if((pParser->Data.Path.Node[pParser->Data.Path.Size] & 0xffff0000) == 0x00ff0000) {
|
||||
pParser->Data.Path.Size--;
|
||||
}
|
||||
|
||||
ResetLocalState(pParser);
|
||||
break;
|
||||
|
||||
case ITEM_FEATURE:
|
||||
case ITEM_INPUT:
|
||||
case ITEM_OUTPUT:
|
||||
if (pParser->UsageTab[0] != 0x00000000) {
|
||||
/* An object was found if the path does not end with 0x00000000 */
|
||||
Found = 1;
|
||||
} else {
|
||||
/* It is a continuation of a previous object */
|
||||
Found = 0;
|
||||
}
|
||||
|
||||
/* Get new pParser->Count from global value */
|
||||
if(pParser->Count == 0) {
|
||||
pParser->Count = pParser->ReportCount;
|
||||
}
|
||||
|
||||
/* Get UPage/Usage from UsageTab and store them in pParser->Data.Path */
|
||||
pParser->Data.Path.Node[pParser->Data.Path.Size] = pParser->UsageTab[0];
|
||||
pParser->Data.Path.Size++;
|
||||
|
||||
/* Unstack UPage/Usage from UsageTab (never remove the last) */
|
||||
if(pParser->UsageSize > 0) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < pParser->UsageSize; i++) {
|
||||
pParser->UsageTab[i] = pParser->UsageTab[i+1];
|
||||
}
|
||||
/* Remove Usage */
|
||||
pParser->UsageSize--;
|
||||
}
|
||||
|
||||
/* Copy data type */
|
||||
pParser->Data.Type = (uint8_t)(pParser->Item & ITEM_MASK);
|
||||
|
||||
/* Copy data attribute */
|
||||
pParser->Data.Attribute = (uint8_t)pParser->Value;
|
||||
|
||||
/* Store offset */
|
||||
pParser->Data.Offset = *GetReportOffset(pParser, pParser->Data.ReportID, (uint8_t)(pParser->Item & ITEM_MASK));
|
||||
|
||||
/* Get Object in pData */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
memcpy(pData, &pParser->Data, sizeof(HIDData_t));
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Increment Report Offset */
|
||||
*GetReportOffset(pParser, pParser->Data.ReportID, (uint8_t)(pParser->Item & ITEM_MASK)) += pParser->Data.Size;
|
||||
|
||||
/* Remove path last node */
|
||||
pParser->Data.Path.Size--;
|
||||
|
||||
/* Decrement count */
|
||||
pParser->Count--;
|
||||
|
||||
if (pParser->Count == 0) {
|
||||
ResetLocalState(pParser);
|
||||
}
|
||||
break;
|
||||
|
||||
case ITEM_REP_ID :
|
||||
pParser->Data.ReportID = (uint8_t)pParser->Value;
|
||||
break;
|
||||
|
||||
case ITEM_REP_SIZE :
|
||||
pParser->Data.Size = (uint8_t)pParser->Value;
|
||||
break;
|
||||
|
||||
case ITEM_REP_COUNT :
|
||||
pParser->ReportCount = (uint8_t)pParser->Value;
|
||||
break;
|
||||
|
||||
case ITEM_UNIT_EXP :
|
||||
pParser->Data.UnitExp = (int8_t)pParser->Value;
|
||||
if (pParser->Data.UnitExp > 7) {
|
||||
pParser->Data.UnitExp |= 0xF0;
|
||||
}
|
||||
break;
|
||||
|
||||
case ITEM_UNIT :
|
||||
pParser->Data.Unit = pParser->Value;
|
||||
break;
|
||||
|
||||
case ITEM_LOG_MIN :
|
||||
pParser->Data.LogMin = FormatValue(pParser->Value, ItemSize[pParser->Item & SIZE_MASK]);
|
||||
break;
|
||||
|
||||
case ITEM_LOG_MAX :
|
||||
pParser->Data.LogMax = FormatValue(pParser->Value, ItemSize[pParser->Item & SIZE_MASK]);
|
||||
break;
|
||||
|
||||
case ITEM_PHY_MIN :
|
||||
pParser->Data.PhyMin=FormatValue(pParser->Value, ItemSize[pParser->Item & SIZE_MASK]);
|
||||
pParser->Data.have_PhyMin = 1;
|
||||
break;
|
||||
|
||||
case ITEM_PHY_MAX :
|
||||
pParser->Data.PhyMax=FormatValue(pParser->Value, ItemSize[pParser->Item & SIZE_MASK]);
|
||||
pParser->Data.have_PhyMax = 1;
|
||||
break;
|
||||
|
||||
case ITEM_LONG :
|
||||
/* can't handle long items, but should at least skip them */
|
||||
pParser->Pos += (uint8_t)(pParser->Value & 0xff);
|
||||
break;
|
||||
}
|
||||
} /* while ((Found < 0) && (pParser->Pos < pParser->ReportDescSize)) */
|
||||
|
||||
ERROR(pParser->Data.Path.Size >= PATH_SIZE);
|
||||
ERROR(pParser->ReportDescSize >= REPORT_DSC_SIZE);
|
||||
ERROR(pParser->UsageSize >= USAGE_TAB_SIZE);
|
||||
ERROR(pParser->Data.ReportID >= MAX_REPORT);
|
||||
|
||||
return Found;
|
||||
}
|
||||
|
||||
/*
|
||||
* FindObject
|
||||
* Get pData characteristics from pData->Path
|
||||
* Return TRUE if object was found
|
||||
* -------------------------------------------------------------------------- */
|
||||
int FindObject(HIDDesc_t *pDesc, HIDData_t *pData)
|
||||
{
|
||||
HIDData_t *pFoundData = FindObject_with_Path(pDesc, &pData->Path, pData->Type);
|
||||
|
||||
if (!pFoundData) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
memcpy(pData, pFoundData, sizeof(*pData));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* FindObject_with_Path
|
||||
* Get pData item with given Path and Type. Return NULL if not found.
|
||||
* -------------------------------------------------------------------------- */
|
||||
HIDData_t *FindObject_with_Path(HIDDesc_t *pDesc, HIDPath_t *Path, uint8_t Type)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < pDesc->nitems; i++) {
|
||||
HIDData_t *pData = &pDesc->item[i];
|
||||
|
||||
if (pData->Type != Type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (memcmp(pData->Path.Node, Path->Node, (Path->Size) * sizeof(HIDNode_t))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return pData;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* FindObject_with_ID
|
||||
* Get pData item with given ReportID, Offset, and Type. Return NULL
|
||||
* if not found.
|
||||
* -------------------------------------------------------------------------- */
|
||||
HIDData_t *FindObject_with_ID(HIDDesc_t *pDesc, uint8_t ReportID, uint8_t Offset, uint8_t Type)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < pDesc->nitems; i++) {
|
||||
HIDData_t *pData = &pDesc->item[i];
|
||||
|
||||
if (pData->ReportID != ReportID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pData->Type != Type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pData->Offset != Offset) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return pData;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* GetValue
|
||||
* Extract data from a report stored in Buf.
|
||||
* Use Value, Offset, Size and LogMax of pData.
|
||||
* Return response in Value.
|
||||
* -------------------------------------------------------------------------- */
|
||||
void GetValue(const unsigned char *Buf, HIDData_t *pData, long *pValue)
|
||||
{
|
||||
int Weight, Bit;
|
||||
long value = 0, rawvalue;
|
||||
long range, mask, signbit, b, m;
|
||||
|
||||
Bit = pData->Offset + 8; /* First byte of report is report ID */
|
||||
|
||||
for (Weight = 0; Weight < pData->Size; Weight++, Bit++) {
|
||||
int State = Buf[Bit >> 3] & (1 << (Bit & 7));
|
||||
|
||||
if(State) {
|
||||
value += (1 << Weight);
|
||||
}
|
||||
}
|
||||
|
||||
/* translate Value into a signed/unsigned value in the range
|
||||
LogMin..LogMax, as appropriate. See HID spec, p.38: "If both the
|
||||
Logical Minimum and Logical Maximum extents are defined as
|
||||
positive values (0 or greater), then the report field can be
|
||||
assumed to be an unsigned value. Otherwise, all integer values
|
||||
are signed values represented in 2's complement format."
|
||||
|
||||
Also note that the variable can take values from LogMin
|
||||
(inclusive) to LogMax (inclusive), so there are LogMax - LogMin +
|
||||
1 possible values.
|
||||
|
||||
Special cases arise if the value that has been read lies outside
|
||||
the interval LogMin..LogMax. Some devices, notably the APC
|
||||
Back-UPS BF500, do this. In one case I observed, LogMin=0,
|
||||
LogMax=0xffff, Size=32, and the supplied value is
|
||||
0xffffffff80080a00. Presumably they expect us to throw away the
|
||||
higher-order bits, and use 0x0a00, rather than choosing the
|
||||
closest value in the interval, which would be 0xffff. However,
|
||||
if LogMax - LogMin + 1 isn't a power of 2, it is not clear what
|
||||
"throwing away higher-order bits" exacly means, so we try to do
|
||||
something sensible. -PS */
|
||||
|
||||
rawvalue = value; /* remember this for later */
|
||||
|
||||
/* figure out how many bits are significant */
|
||||
range = pData->LogMax - pData->LogMin + 1;
|
||||
if (range <= 0) {
|
||||
/* makes no sense, give up */
|
||||
*pValue = value;
|
||||
return;
|
||||
}
|
||||
b = hibit(range-1);
|
||||
|
||||
/* throw away insignificant bits; the result is >= 0 */
|
||||
mask = (1 << b) - 1;
|
||||
signbit = 1 << (b - 1);
|
||||
value = value & mask;
|
||||
|
||||
/* sign-extend it, if appropriate */
|
||||
if (pData->LogMin < 0 && (value & signbit) != 0) {
|
||||
value |= ~mask;
|
||||
}
|
||||
|
||||
/* if the resulting value is in the desired range, stop */
|
||||
if (value >= pData->LogMin && value <= pData->LogMax) {
|
||||
*pValue = value;
|
||||
return;
|
||||
}
|
||||
|
||||
/* else, try to reach interval by adjusting high-order bits */
|
||||
m = (value - pData->LogMin) & mask;
|
||||
value = pData->LogMin + m;
|
||||
if (value <= pData->LogMax) {
|
||||
*pValue = value;
|
||||
return;
|
||||
}
|
||||
|
||||
/* if everything else failed, sign-extend the original raw value,
|
||||
and simply round it to the closest point in the interval. */
|
||||
value = rawvalue;
|
||||
mask = (1 << pData->Size) - 1;
|
||||
signbit = 1 << (pData->Size - 1);
|
||||
if (pData->LogMin < 0 && (value & signbit) != 0) {
|
||||
value |= ~mask;
|
||||
}
|
||||
if (value < pData->LogMin) {
|
||||
value = pData->LogMin;
|
||||
} else if (value > pData->LogMax) {
|
||||
value = pData->LogMax;
|
||||
}
|
||||
|
||||
*pValue = value;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* SetValue
|
||||
* Set a data in a report stored in Buf. Use Value, Offset and Size of pData.
|
||||
* Return response in Buf.
|
||||
* -------------------------------------------------------------------------- */
|
||||
void SetValue(const HIDData_t *pData, unsigned char *Buf, long Value)
|
||||
{
|
||||
int Weight, Bit;
|
||||
|
||||
Bit = pData->Offset + 8; /* First byte of report is report ID */
|
||||
|
||||
for (Weight = 0; Weight < pData->Size; Weight++, Bit++) {
|
||||
int State = Value & (1 << Weight);
|
||||
|
||||
if (State) {
|
||||
Buf[Bit >> 3] |= (1 << (Bit & 7));
|
||||
} else {
|
||||
Buf[Bit >> 3] &= ~(1 << (Bit & 7));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/* parse HID Report Descriptor. Input: byte array ReportDesc[n].
|
||||
Output: parsed data structure. Returns allocated HIDDesc structure
|
||||
on success, NULL on failure with errno set. Note: the value
|
||||
returned by this function must be freed with Free_ReportDesc(). */
|
||||
HIDDesc_t *Parse_ReportDesc(const unsigned char *ReportDesc, const int n)
|
||||
{
|
||||
int ret;
|
||||
HIDDesc_t *pDesc;
|
||||
HIDParser_t *parser;
|
||||
|
||||
pDesc = calloc(1, sizeof(*pDesc));
|
||||
if (!pDesc) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pDesc->item = calloc(MAX_REPORT, sizeof(*pDesc->item));
|
||||
if (!pDesc->item) {
|
||||
Free_ReportDesc(pDesc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
parser = calloc(1, sizeof(*parser));
|
||||
if (!parser) {
|
||||
Free_ReportDesc(pDesc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
parser->ReportDesc = ReportDesc;
|
||||
parser->ReportDescSize = n;
|
||||
|
||||
for (pDesc->nitems = 0; pDesc->nitems < MAX_REPORT; pDesc->nitems += ret) {
|
||||
int id, max;
|
||||
|
||||
ret = HIDParse(parser, &pDesc->item[pDesc->nitems]);
|
||||
if (ret < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
id = pDesc->item[pDesc->nitems].ReportID;
|
||||
|
||||
/* calculate bit range of this item within report */
|
||||
max = pDesc->item[pDesc->nitems].Offset + pDesc->item[pDesc->nitems].Size;
|
||||
|
||||
/* convert to bytes */
|
||||
max = (max + 7) >> 3;
|
||||
|
||||
/* update report length */
|
||||
if (max > pDesc->replen[id]) {
|
||||
pDesc->replen[id] = max;
|
||||
}
|
||||
}
|
||||
|
||||
free(parser);
|
||||
|
||||
if (pDesc->nitems == 0) {
|
||||
Free_ReportDesc(pDesc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pDesc->item = realloc(pDesc->item, pDesc->nitems * sizeof(*pDesc->item));
|
||||
|
||||
return pDesc;
|
||||
}
|
||||
|
||||
/* free a parsed report descriptor, as allocated by Parse_ReportDesc() */
|
||||
void Free_ReportDesc(HIDDesc_t *pDesc)
|
||||
{
|
||||
if (!pDesc) {
|
||||
return;
|
||||
}
|
||||
|
||||
free(pDesc->item);
|
||||
free(pDesc);
|
||||
}
|
||||
69
drivers/hidparser.h
Normal file
69
drivers/hidparser.h
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* hidparser.h: HID Parser header file
|
||||
*
|
||||
* This file is part of the MGE UPS SYSTEMS HID Parser.
|
||||
*
|
||||
* Copyright (C) 1998-2003 MGE UPS SYSTEMS,
|
||||
* Written by Luc Descotils.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
#ifndef HIDPARS_H
|
||||
#define HIDPARS_H
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#include "hidtypes.h"
|
||||
|
||||
/*
|
||||
* Parse_ReportDesc
|
||||
* -------------------------------------------------------------------------- */
|
||||
HIDDesc_t *Parse_ReportDesc(const unsigned char *ReportDesc, const int n);
|
||||
|
||||
/*
|
||||
* Free_ReportDesc
|
||||
* -------------------------------------------------------------------------- */
|
||||
void Free_ReportDesc(HIDDesc_t *pDesc);
|
||||
|
||||
/*
|
||||
* FindObject
|
||||
* -------------------------------------------------------------------------- */
|
||||
int FindObject(HIDDesc_t *pDesc, HIDData_t *pData);
|
||||
|
||||
HIDData_t *FindObject_with_Path(HIDDesc_t *pDesc, HIDPath_t *Path, uint8_t Type);
|
||||
|
||||
HIDData_t *FindObject_with_ID(HIDDesc_t *pDesc, uint8_t ReportID, uint8_t Offset, uint8_t Type);
|
||||
|
||||
/*
|
||||
* GetValue
|
||||
* -------------------------------------------------------------------------- */
|
||||
void GetValue(const unsigned char *Buf, HIDData_t *pData, long *pValue);
|
||||
|
||||
/*
|
||||
* SetValue
|
||||
* -------------------------------------------------------------------------- */
|
||||
void SetValue(const HIDData_t *pData, unsigned char *Buf, long Value);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif
|
||||
149
drivers/hidtypes.h
Normal file
149
drivers/hidtypes.h
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* types.h: HID Parser types definitions
|
||||
*
|
||||
* This file is part of the MGE UPS SYSTEMS HID Parser
|
||||
*
|
||||
* Copyright (C)
|
||||
* 1998-2003 MGE UPS SYSTEMS, Luc Descotils
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
#ifndef HIDTYPES_H
|
||||
#define HIDTYPES_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "nut_stdint.h"
|
||||
|
||||
/*
|
||||
* Constants
|
||||
* -------------------------------------------------------------------------- */
|
||||
#define PATH_SIZE 10 /* Deep max for Path */
|
||||
#define USAGE_TAB_SIZE 50 /* Size of usage stack */
|
||||
#define MAX_REPORT 300 /* Including FEATURE, INPUT and OUTPUT */
|
||||
#define REPORT_DSC_SIZE 6144 /* Size max of Report Descriptor */
|
||||
#define MAX_REPORT_TS 3 /* Max time validity of a report */
|
||||
|
||||
/*
|
||||
* Items
|
||||
* -------------------------------------------------------------------------- */
|
||||
#define SIZE_0 0x00
|
||||
#define SIZE_1 0x01
|
||||
#define SIZE_2 0x02
|
||||
#define SIZE_4 0x03
|
||||
#define SIZE_MASK 0x03
|
||||
|
||||
#define TYPE_MAIN 0x00
|
||||
#define TYPE_GLOBAL 0x04
|
||||
#define TYPE_LOCAL 0x08
|
||||
#define TYPE_MASK 0x0C
|
||||
|
||||
/* Main items */
|
||||
#define ITEM_COLLECTION 0xA0
|
||||
#define ITEM_END_COLLECTION 0xC0
|
||||
#define ITEM_FEATURE 0xB0
|
||||
#define ITEM_INPUT 0x80
|
||||
#define ITEM_OUTPUT 0x90
|
||||
|
||||
/* Global items */
|
||||
#define ITEM_UPAGE 0x04
|
||||
#define ITEM_LOG_MIN 0x14
|
||||
#define ITEM_LOG_MAX 0x24
|
||||
#define ITEM_PHY_MIN 0x34
|
||||
#define ITEM_PHY_MAX 0x44
|
||||
#define ITEM_UNIT_EXP 0x54
|
||||
#define ITEM_UNIT 0x64
|
||||
#define ITEM_REP_SIZE 0x74
|
||||
#define ITEM_REP_ID 0x84
|
||||
#define ITEM_REP_COUNT 0x94
|
||||
|
||||
/* Local items */
|
||||
#define ITEM_USAGE 0x08
|
||||
#define ITEM_STRING 0x78
|
||||
|
||||
/* Long item */
|
||||
#define ITEM_LONG 0xFC
|
||||
|
||||
#define ITEM_MASK 0xFC
|
||||
|
||||
/* Attribute Flags */
|
||||
#define ATTR_DATA_CST 0x01
|
||||
#define ATTR_NVOL_VOL 0x80
|
||||
|
||||
/*
|
||||
* HIDNode_t struct
|
||||
*
|
||||
* Describe a HID Path point: Usage = bits 0..15, UPage = bits 16..31
|
||||
* -------------------------------------------------------------------------- */
|
||||
typedef uint32_t HIDNode_t;
|
||||
|
||||
/*
|
||||
* HIDPath struct
|
||||
*
|
||||
* Describe a HID Path
|
||||
* -------------------------------------------------------------------------- */
|
||||
typedef struct {
|
||||
uint8_t Size; /* HID Path size */
|
||||
HIDNode_t Node[PATH_SIZE]; /* HID Path */
|
||||
} HIDPath_t;
|
||||
|
||||
/*
|
||||
* HIDData struct
|
||||
*
|
||||
* Describe a HID Data with its location in report
|
||||
* -------------------------------------------------------------------------- */
|
||||
typedef struct {
|
||||
HIDPath_t Path; /* HID Path */
|
||||
|
||||
uint8_t ReportID; /* Report ID */
|
||||
uint8_t Offset; /* Offset of data in report */
|
||||
uint8_t Size; /* Size of data in bit */
|
||||
|
||||
uint8_t Type; /* Type : FEATURE / INPUT / OUTPUT */
|
||||
uint8_t Attribute; /* Report field attribute */
|
||||
|
||||
long Unit; /* HID Unit */
|
||||
int8_t UnitExp; /* Unit exponent */
|
||||
|
||||
long LogMin; /* Logical Min */
|
||||
long LogMax; /* Logical Max */
|
||||
long PhyMin; /* Physical Min */
|
||||
long PhyMax; /* Physical Max */
|
||||
int8_t have_PhyMin; /* Physical Min defined? */
|
||||
int8_t have_PhyMax; /* Physical Max defined? */
|
||||
} HIDData_t;
|
||||
|
||||
/*
|
||||
* HIDDesc struct
|
||||
*
|
||||
* Holds a parsed report descriptor
|
||||
* -------------------------------------------------------------------------- */
|
||||
typedef struct {
|
||||
int nitems; /* number of items in descriptor */
|
||||
HIDData_t *item; /* list of items */
|
||||
int replen[256]; /* list of report lengths, in byte */
|
||||
} HIDDesc_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* HIDTYPES_H */
|
||||
258
drivers/ietf-mib.c
Normal file
258
drivers/ietf-mib.c
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
/* ietf-mib.c - data to monitor SNMP UPS (RFC 1628 compliant) with NUT
|
||||
*
|
||||
* Copyright (C) 2002-2006
|
||||
* Arnaud Quette <arnaud.quette@free.fr>
|
||||
* Niels Baggesen <niels@baggesen.net>
|
||||
*
|
||||
* Sponsored by MGE UPS SYSTEMS <http://www.mgeups.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ietf-mib.h"
|
||||
|
||||
#define IETF_MIB_VERSION "1.3"
|
||||
|
||||
/* SNMP OIDs set */
|
||||
#define IETF_OID_UPS_MIB "1.3.6.1.2.1.33"
|
||||
#define IETF_OID_MFR_NAME "1.3.6.1.2.1.33.1.1.1.0" /* UPS-MIB::upsIdentManufacturer.0 */
|
||||
#define IETF_OID_MODEL_NAME "1.3.6.1.2.1.33.1.1.2.0" /* UPS-MIB::upsIdentModel.0 */
|
||||
#define IETF_OID_FIRMREV "1.3.6.1.2.1.33.1.1.3.0" /* UPS-MIB::upsIdentUPSSoftwareVersion.0 */
|
||||
#define IETF_OID_AGENTREV "1.3.6.1.2.1.33.1.1.4.0" /* UPS-MIB::upsIdentAgentSoftwareVersion.0 */
|
||||
#define IETF_OID_IDENT "1.3.6.1.2.1.33.1.1.5.0" /* UPS-MIB::upsIdentName.0 */
|
||||
|
||||
#define IETF_OID_BATT_STATUS "1.3.6.1.2.1.33.1.2.1.0" /* UPS-MIB::upsBatteryStatus.0 */
|
||||
#define IETF_OID_BATT_RUNTIME "1.3.6.1.2.1.33.1.2.3.0" /* UPS-MIB::upsEstimatedMinutesRemaining.0 */
|
||||
#define IETF_OID_BATT_CHARGE "1.3.6.1.2.1.33.1.2.4.0" /* UPS-MIB::upsEstimatedChargeRemaining.0 */
|
||||
#define IETF_OID_BATT_VOLTAGE "1.3.6.1.2.1.33.1.2.5.0" /* UPS-MIB::upsBatteryVoltage.0 */
|
||||
#define IETF_OID_BATT_CURRENT "1.3.6.1.2.1.33.1.2.6.0" /* UPS-MIB::upsBatteryCurrent.0 */
|
||||
#define IETF_OID_BATT_TEMP "1.3.6.1.2.1.33.1.2.7.0" /* UPS-MIB::upsBatteryTemperature.0 */
|
||||
|
||||
#define IETF_OID_IN_LINEBADS "1.3.6.1.2.1.33.1.3.1.0" /* UPS-MIB::upsInputLineBads.0 */
|
||||
#define IETF_OID_IN_LINES "1.3.6.1.2.1.33.1.3.2.0" /* UPS-MIB::upsInputNumLines.0 */
|
||||
|
||||
#define IETF_OID_IN_FREQ "1.3.6.1.2.1.33.1.3.3.1.2" /* UPS-MIB::upsInputFrequency */
|
||||
#define IETF_OID_IN_VOLTAGE "1.3.6.1.2.1.33.1.3.3.1.3" /* UPS-MIB::upsInputVoltage */
|
||||
#define IETF_OID_IN_CURRENT "1.3.6.1.2.1.33.1.3.3.1.4" /* UPS-MIB::upsInputCurrent */
|
||||
#define IETF_OID_IN_POWER "1.3.6.1.2.1.33.1.3.3.1.5" /* UPS-MIB::upsInputTruePower */
|
||||
|
||||
#define IETF_OID_POWER_STATUS "1.3.6.1.2.1.33.1.4.1.0" /* UPS-MIB::upsOutputSource.0 */
|
||||
#define IETF_OID_OUT_FREQUENCY "1.3.6.1.2.1.33.1.4.2.0" /* UPS-MIB::upsOutputFrequency.0 */
|
||||
#define IETF_OID_OUT_LINES "1.3.6.1.2.1.33.1.4.3.0" /* UPS-MIB::upsOutputNumLines.0 */
|
||||
|
||||
#define IETF_OID_OUT_VOLTAGE "1.3.6.1.2.1.33.1.4.4.1.2" /* UPS-MIB::upsOutputVoltage */
|
||||
#define IETF_OID_OUT_CURRENT "1.3.6.1.2.1.33.1.4.4.1.3" /* UPS-MIB::upsOutputCurrent */
|
||||
#define IETF_OID_OUT_POWER "1.3.6.1.2.1.33.1.4.4.1.4" /* UPS-MIB::upsOutputPower */
|
||||
#define IETF_OID_LOAD_LEVEL "1.3.6.1.2.1.33.1.4.4.1.5" /* UPS-MIB::upsOutputPercentLoad */
|
||||
|
||||
#define IETF_OID_UPS_TEST_ID "1.3.6.1.2.1.33.1.7.1" /* UPS-MIB::upsTestID */
|
||||
#define IETF_OID_UPS_TEST_RES "1.3.6.1.2.1.33.1.7.3" /* UPS-MIB::upsTestResultsSummary */
|
||||
#define IETF_OID_UPS_TEST_RESDET "1.3.6.1.2.1.33.1.7.4" /* UPS-MIB::upsTestResultsDetail */
|
||||
#define IETF_OID_UPS_TEST_NOTEST "1.3.6.1.2.1.33.1.7.7.1" /* UPS-MIB::upsTestNoTestInitiated */
|
||||
#define IETF_OID_UPS_TEST_ABORT "1.3.6.1.2.1.33.1.7.7.2" /* UPS-MIB::upsTestAbortTestInProgress */
|
||||
#define IETF_OID_UPS_TEST_GSTEST "1.3.6.1.2.1.33.1.7.7.3" /* UPS-MIB::upsTestGeneralSystemsTest */
|
||||
#define IETF_OID_UPS_TEST_QBATT "1.3.6.1.2.1.33.1.7.7.4" /* UPS-MIB::upsTestQuickBatteryTest */
|
||||
#define IETF_OID_UPS_TEST_DBATT "1.3.6.1.2.1.33.1.7.7.5" /* UPS-MIB::upsTestDeepBatteryCalibration */
|
||||
|
||||
#define IETF_OID_CONF_VOLTAGE "1.3.6.1.2.1.33.1.9.3.0" /* UPS-MIB::upsConfigOutputVoltage.0 */
|
||||
#define IETF_OID_CONF_OUT_VA "1.3.6.1.2.1.33.1.9.5.0" /* UPS-MIB::upsConfigOutputVA.0 */
|
||||
#define IETF_OID_CONF_RUNTIME_LOW "1.3.6.1.2.1.33.1.9.7.0" /* UPS-MIB::upsConfigLowBattTime.0 */
|
||||
|
||||
/* Defines for IETF_OID_POWER_STATUS (1) */
|
||||
static info_lkp_t ietf_pwr_info[] = {
|
||||
{ 1, "" /* other */ },
|
||||
{ 2, "OFF" /* none */ },
|
||||
{ 3, "OL" /* normal */ },
|
||||
{ 4, "OL BYPASS" /* bypass */ },
|
||||
{ 5, "OB" /* battery */ },
|
||||
{ 6, "OL BOOST" /* booster */ },
|
||||
{ 7, "OL TRIM" /* reducer */ },
|
||||
{ 0, "NULL" }
|
||||
} ;
|
||||
|
||||
/* Defines for IETF_OID_BATT_STATUS (2) */
|
||||
static info_lkp_t ietf_batt_info[] = {
|
||||
{ 1, "" /* unknown */ },
|
||||
{ 2, "" /* batteryNormal */},
|
||||
{ 3, "LB" /* batteryLow */ },
|
||||
{ 4, "LB" /* batteryDepleted */ },
|
||||
{ 5, "" /* unknown */ },
|
||||
{ 6, "RB" /* batteryError */},
|
||||
{ 0, "NULL" }
|
||||
} ;
|
||||
|
||||
/* Defines for IETF_OID_TEST_RES */
|
||||
static info_lkp_t ietf_test_res_info[] = {
|
||||
{ 1, "Done and passed" },
|
||||
{ 2, "Done and warning" },
|
||||
{ 3, "Done and error" },
|
||||
{ 4, "Aborted" },
|
||||
{ 5, "In progress" },
|
||||
{ 6, "No test initiated" },
|
||||
{ 0, "NULL" }
|
||||
} ;
|
||||
|
||||
#define IETF_OID_SD_AFTER_DELAY "1.3.6.1.2.1.33.1.8.2" /* UPS-MIB::upsShutdownAfterDelay */
|
||||
#define IETF_OFF_DO 0
|
||||
|
||||
#define IETF_OID_ALARM_OB "1.3.6.1.2.1.33.1.6.3.2" /* UPS-MIB::upsAlarmOnBattery */
|
||||
#define IETF_OID_ALARM_LB "1.3.6.1.2.1.33.1.6.3.3" /* UPS-MIB::upsAlarmLowBattery */
|
||||
|
||||
static info_lkp_t ietf_alarm_ob[] = {
|
||||
{ 1, "OB" },
|
||||
{ 0, "NULL" }
|
||||
} ;
|
||||
|
||||
static info_lkp_t ietf_alarm_lb[] = {
|
||||
{ 1, "LB" },
|
||||
{ 0, "NULL" }
|
||||
} ;
|
||||
|
||||
/* Missing data
|
||||
CAL - UPS is performing calibration
|
||||
OVER - UPS is overloaded
|
||||
FSD - UPS is in forced shutdown state (slaves take note)
|
||||
*/
|
||||
|
||||
/* Snmp2NUT lookup table */
|
||||
|
||||
static snmp_info_t ietf_mib[] = {
|
||||
/* UPS page */
|
||||
/* info_type, info_flags, info_len, OID, dfl, flags, oid2info, setvar */
|
||||
{ "ups.mfr", ST_FLAG_STRING, SU_INFOSIZE, IETF_OID_MFR_NAME, "Generic",
|
||||
SU_FLAG_STATIC, NULL },
|
||||
{ "ups.model", ST_FLAG_STRING, SU_INFOSIZE, IETF_OID_MODEL_NAME, "Generic SNMP UPS",
|
||||
SU_FLAG_STATIC, NULL },
|
||||
{ "ups.firmware", ST_FLAG_STRING, SU_INFOSIZE, IETF_OID_FIRMREV, "",
|
||||
SU_FLAG_STATIC, NULL },
|
||||
{ "ups.firmware.aux", ST_FLAG_STRING, SU_INFOSIZE, IETF_OID_AGENTREV, "",
|
||||
SU_FLAG_STATIC, NULL },
|
||||
{ "ups.serial", ST_FLAG_STRING, SU_INFOSIZE, IETF_OID_IDENT, "",
|
||||
SU_FLAG_STATIC, NULL },
|
||||
{ "ups.status", ST_FLAG_STRING, SU_INFOSIZE, IETF_OID_POWER_STATUS, "OFF",
|
||||
SU_STATUS_PWR, &ietf_pwr_info[0] },
|
||||
{ "ups.status", ST_FLAG_STRING, SU_INFOSIZE, IETF_OID_BATT_STATUS, "",
|
||||
SU_STATUS_BATT, &ietf_alarm_ob[0] },
|
||||
{ "ups.status", ST_FLAG_STRING, SU_INFOSIZE, IETF_OID_BATT_STATUS, "",
|
||||
SU_STATUS_BATT, &ietf_alarm_lb[0] },
|
||||
{ "ups.status", ST_FLAG_STRING, SU_INFOSIZE, IETF_OID_BATT_STATUS, "",
|
||||
SU_STATUS_BATT, &ietf_batt_info[0] },
|
||||
{ "ups.test.result", ST_FLAG_STRING, SU_INFOSIZE, IETF_OID_UPS_TEST_RESDET, "",
|
||||
0, NULL },
|
||||
{ "ups.test.result", ST_FLAG_STRING, SU_INFOSIZE, IETF_OID_UPS_TEST_RES, "",
|
||||
0, ietf_test_res_info },
|
||||
|
||||
/* Battery page */
|
||||
{ "battery.charge", 0, 1.0, IETF_OID_BATT_CHARGE, "",
|
||||
0, NULL },
|
||||
{ "battery.runtime", 0, 60.0, IETF_OID_BATT_RUNTIME, "",
|
||||
0, NULL },
|
||||
{ "battery.runtime.low", ST_FLAG_RW, 1, IETF_OID_CONF_RUNTIME_LOW, "",
|
||||
0, NULL },
|
||||
{ "battery.voltage", 0, 0.1, IETF_OID_BATT_VOLTAGE, "",
|
||||
0, NULL },
|
||||
{ "battery.current", 0, 0.1, IETF_OID_BATT_CURRENT, "",
|
||||
0, NULL },
|
||||
{ "battery.temperature", 0, 1.0, IETF_OID_BATT_TEMP, "",
|
||||
0, NULL },
|
||||
|
||||
/* Output page */
|
||||
{ "output.phases", 0, 1.0, IETF_OID_OUT_LINES, "",
|
||||
SU_FLAG_SETINT, NULL, &output_phases },
|
||||
{ "output.frequency", 0, 0.1, IETF_OID_OUT_FREQUENCY, "",
|
||||
0, NULL },
|
||||
{ "output.voltage", 0, 1.0, IETF_OID_OUT_VOLTAGE ".1", "",
|
||||
SU_OUTPUT_1, NULL },
|
||||
{ "output.L1-N.voltage", 0, 1.0, IETF_OID_OUT_VOLTAGE ".1", "",
|
||||
SU_OUTPUT_3, NULL },
|
||||
{ "output.L2-N.voltage", 0, 1.0, IETF_OID_OUT_VOLTAGE ".2", "",
|
||||
SU_OUTPUT_3, NULL },
|
||||
{ "output.L3-N.voltage", 0, 1.0, IETF_OID_OUT_VOLTAGE ".3", "",
|
||||
SU_OUTPUT_3, NULL },
|
||||
{ "output.current", 0, 0.1, IETF_OID_OUT_CURRENT ".1", "",
|
||||
SU_OUTPUT_1, NULL },
|
||||
{ "output.L1.current", 0, 0.1, IETF_OID_OUT_CURRENT ".1", "",
|
||||
SU_OUTPUT_3, NULL },
|
||||
{ "output.L2.current", 0, 0.1, IETF_OID_OUT_CURRENT ".2", "",
|
||||
SU_OUTPUT_3, NULL },
|
||||
{ "output.L3.current", 0, 0.1, IETF_OID_OUT_CURRENT ".3", "",
|
||||
SU_OUTPUT_3, NULL },
|
||||
{ "output.power", 0, 1.0, IETF_OID_OUT_POWER ".1", "",
|
||||
SU_OUTPUT_1, NULL },
|
||||
{ "output.L1.power", 0, 1.0, IETF_OID_OUT_POWER ".1", "",
|
||||
SU_OUTPUT_3, NULL },
|
||||
{ "output.L2.power", 0, 1.0, IETF_OID_OUT_POWER ".2", "",
|
||||
SU_OUTPUT_3, NULL },
|
||||
{ "output.L3.power", 0, 1.0, IETF_OID_OUT_POWER ".3", "",
|
||||
SU_OUTPUT_3, NULL },
|
||||
{ "ups.load", 0, 1.0, IETF_OID_LOAD_LEVEL ".1", "",
|
||||
SU_OUTPUT_1, NULL },
|
||||
{ "output.L1.power.percent", 0, 1.0, IETF_OID_LOAD_LEVEL ".1", "",
|
||||
SU_OUTPUT_3, NULL },
|
||||
{ "output.L2.power.percent", 0, 1.0, IETF_OID_LOAD_LEVEL ".2", "",
|
||||
SU_OUTPUT_3, NULL },
|
||||
{ "output.L3.power.percent", 0, 1.0, IETF_OID_LOAD_LEVEL ".3", "",
|
||||
SU_OUTPUT_3, NULL },
|
||||
|
||||
/* Input page */
|
||||
{ "input.phases", 0, 1.0, IETF_OID_IN_LINES, "",
|
||||
SU_FLAG_SETINT, NULL, &input_phases },
|
||||
{ "input.frequency", 0, 0.1, IETF_OID_IN_FREQ ".1", "",
|
||||
SU_INPUT_1, NULL },
|
||||
{ "input.voltage", 0, 1.0, IETF_OID_IN_VOLTAGE ".1", "",
|
||||
SU_INPUT_1, NULL },
|
||||
{ "input.L1-N.voltage", 0, 1.0, IETF_OID_IN_VOLTAGE ".1", "",
|
||||
SU_INPUT_3, NULL },
|
||||
{ "input.L2-N.voltage", 0, 1.0, IETF_OID_IN_VOLTAGE ".2", "",
|
||||
SU_INPUT_3, NULL },
|
||||
{ "input.L3-N.voltage", 0, 1.0, IETF_OID_IN_VOLTAGE ".3", "",
|
||||
SU_INPUT_3, NULL },
|
||||
{ "input.current", 0, 0.1, IETF_OID_IN_CURRENT ".1", "",
|
||||
SU_INPUT_1, NULL },
|
||||
{ "input.L1.current", 0, 0.1, IETF_OID_IN_CURRENT ".1", "",
|
||||
SU_INPUT_3, NULL },
|
||||
{ "input.L2.current", 0, 0.1, IETF_OID_IN_CURRENT ".2", "",
|
||||
SU_INPUT_3, NULL },
|
||||
{ "input.L3.current", 0, 0.1, IETF_OID_IN_CURRENT ".3", "",
|
||||
SU_INPUT_3, NULL },
|
||||
{ "input.realpower", 0, 0.1, IETF_OID_IN_POWER ".1", "",
|
||||
SU_INPUT_1, NULL },
|
||||
{ "input.L1.realpower", 0, 0.1, IETF_OID_IN_POWER ".1", "",
|
||||
SU_INPUT_3, NULL },
|
||||
{ "input.L2.realpower", 0, 0.1, IETF_OID_IN_POWER ".2", "",
|
||||
SU_INPUT_3, NULL },
|
||||
{ "input.L3.realpower", 0, 0.1, IETF_OID_IN_POWER ".3", "",
|
||||
SU_INPUT_3, NULL },
|
||||
{ "input.quality", 0, 1.0, IETF_OID_IN_LINEBADS, "",
|
||||
0, NULL },
|
||||
|
||||
/* instant commands. */
|
||||
{ "load.off", 0, IETF_OFF_DO, IETF_OID_SD_AFTER_DELAY, "",
|
||||
SU_TYPE_CMD, NULL },
|
||||
/* write the OID of the battery test into the test initiator OID */
|
||||
{ "test.battery.start.quick", 0, SU_INFOSIZE, IETF_OID_UPS_TEST_ID, IETF_OID_UPS_TEST_QBATT,
|
||||
SU_TYPE_CMD, NULL },
|
||||
/* write the OID of the battery test into the test initiator OID */
|
||||
{ "test.battery.start.deep", 0, SU_INFOSIZE, IETF_OID_UPS_TEST_ID, IETF_OID_UPS_TEST_DBATT,
|
||||
SU_TYPE_CMD, NULL },
|
||||
/* { CMD_SHUTDOWN, 0, IETF_OFF_GRACEFUL, IETF_OID_OFF, "", 0, NULL }, */
|
||||
|
||||
/* end of structure. */
|
||||
{ NULL, 0, 0, NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
mib2nut_info_t ietf = { "ietf", IETF_MIB_VERSION, IETF_OID_POWER_STATUS, IETF_OID_MFR_NAME, ietf_mib };
|
||||
9
drivers/ietf-mib.h
Normal file
9
drivers/ietf-mib.h
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#ifndef IETF_MIB_H
|
||||
#define IETF_MIB_H
|
||||
|
||||
#include "main.h"
|
||||
#include "snmp-ups.h"
|
||||
|
||||
extern mib2nut_info_t ietf;
|
||||
|
||||
#endif /* IETF_MIB_H */
|
||||
353
drivers/isbmex.c
Normal file
353
drivers/isbmex.c
Normal file
|
|
@ -0,0 +1,353 @@
|
|||
/* isbmex.c - model specific routines for SOLA/BASIC Mexico (ISBMEX) models
|
||||
|
||||
Copyright (C) 2005 Ricardo Martinezgarza <ricardo@nexxis.com.mx>
|
||||
Copyright (C) 2002 Edscott Wilson Garcia <edscott@imp.mx>
|
||||
Copyright (C) 1999 Russell Kroll <rkroll@exploits.org>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "serial.h"
|
||||
|
||||
#include <math.h> /* for sqrt */
|
||||
#include <string.h>
|
||||
|
||||
#define DRIVER_NAME "ISBMEX UPS driver"
|
||||
#define DRIVER_VERSION "0.06"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Ricardo Martinezgarza <ricardo@nexxis.com.mx>\n" \
|
||||
"Edscott Wilson Garcia <edscott@imp.mx>\n" \
|
||||
"Russell Kroll <rkroll@exploits.org>",
|
||||
DRV_STABLE,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
#define xDEBUG
|
||||
|
||||
#ifdef DEBUG
|
||||
#define D(x) x
|
||||
#else
|
||||
#define D(x)
|
||||
#endif
|
||||
|
||||
/*#define ENDCHAR '&'*/
|
||||
#define MAXTRIES 15
|
||||
/* #define IGNCHARS "" */
|
||||
|
||||
float lagrange(unsigned int vbyte)
|
||||
{
|
||||
float f0, f1, f2, f3, f4, f5, f6;
|
||||
float a, b, c, d, e, g, h;
|
||||
const float x0=144.0, x1=154.0, x2=184.0, x3=195.0, x4=215.0, x5=228.0, x6=249.0;
|
||||
const float fX0=95.1, fX1=98.3, fX2=112.6, fX3=116.5, fX4=126.0, fX5=131.0, fX6=140.3;
|
||||
|
||||
if(vbyte < 144) return 0.0;
|
||||
|
||||
f0 = vbyte - x0;
|
||||
f1 = vbyte - x1;
|
||||
f2 = vbyte - x2;
|
||||
f3 = vbyte - x3;
|
||||
f4 = vbyte - x4;
|
||||
f5 = vbyte - x5;
|
||||
f6 = vbyte - x6;
|
||||
|
||||
b = (f1 * f2 * f3 * f4 * f5 * f6 * fX0)/((x0 - x1) * (x0 - x2));
|
||||
b = b / ((x0 - x3) * (x0 - x4));
|
||||
b = b / ((x0 - x5) * (x0 - x6));
|
||||
|
||||
c = (f0 * f2 * f3 * f4 * f5 * f6 * fX1)/((x1 - x0) * (x1 - x2));
|
||||
c = c / ((x1 - x3) * (x1 - x4));
|
||||
c = c / ((x1 - x5) * (x1 - x6));
|
||||
|
||||
d = (f0 * f1 * f3 * f4 * f5 * f6 * fX2)/((x2 - x0) * (x2 - x1));
|
||||
d = d / ((x2 - x3) * (x2 - x4));
|
||||
d = d / ((x2 - x5) * (x2 - x6));
|
||||
|
||||
e = (f0 * f1 * f2 * f4 * f5 * f6 * fX3)/((x3 - x0) * (x3 - x1));
|
||||
e = e / ((x3 - x2) * (x3 - x4));
|
||||
e = e / ((x3 - x5) * (x3 - x6));
|
||||
|
||||
a = (f0 * f1 * f2 * f3 * f5 * f6 * fX4)/((x4 - x0) * (x4 - x1));
|
||||
a = a / ((x4 - x2) * (x4 - x3));
|
||||
a = a / ((x4 - x5) * (x4 - x6));
|
||||
|
||||
g = (f0 * f1 * f2 * f3 * f4 * f6 * fX5)/((x5 - x0) * (x5 - x1));
|
||||
g = g / ((x5 - x2) * (x5 - x3));
|
||||
g = g / ((x5 - x4) * (x5 - x6));
|
||||
|
||||
h = (f0 * f1 * f2 * f3 * f4 * f5 * fX6)/((x6 - x0) * (x6 - x1));
|
||||
h = h / ((x6 - x2) * (x6 - x3));
|
||||
h = h / ((x6 - x4) * (x6 - x5));
|
||||
|
||||
return a + b + c + d + e + g + h;
|
||||
}
|
||||
|
||||
float interpol(float vbytes)
|
||||
{
|
||||
const int x[7]={75,83,87,98,103,118,145};
|
||||
const float f[7]={96.0,102.0,105.0,113.0,116.0,124.0,140.0};
|
||||
float l[7];
|
||||
float t, volts;
|
||||
const int n=6;
|
||||
int i, j;
|
||||
|
||||
if(vbytes < x[0]) return 0.0;
|
||||
if(vbytes > x[6]) return f[6];
|
||||
for(i=0; i<=n; i++)
|
||||
{
|
||||
if((int)vbytes == x[i]) return f[i];
|
||||
}
|
||||
for(i=0; i<=n; i++)
|
||||
{
|
||||
l[i] = 1.0;
|
||||
for(j=0; j<=n; j++)
|
||||
{
|
||||
if(j!=i) l[i] *= (float)(x[i] - x[j]);
|
||||
}
|
||||
l[i] = f[i] / l[i];
|
||||
}
|
||||
t = 1.0;
|
||||
for(i=0; i<=n; i++) t *= (vbytes - (float)x[i]);
|
||||
volts = 0.0;
|
||||
for(i=0; i<=n; i++) volts += (l[i] * t / (vbytes - (float)x[i]));
|
||||
return volts;
|
||||
}
|
||||
|
||||
void upsdrv_initinfo(void)
|
||||
{
|
||||
dstate_setinfo("ups.mfr", "Sola/Basic Mexico");
|
||||
dstate_setinfo("ups.model", "SR-Inet 280/300/400/480/500/800/1000");
|
||||
|
||||
/* high/low voltage */
|
||||
dstate_setinfo("input.transfer.low", "102.0"); /* defined */
|
||||
dstate_setinfo("input.transfer.high", "140.0"); /* defined */
|
||||
|
||||
dstate_setinfo("output.voltage", "120.0"); /* defined */
|
||||
|
||||
/* addinfo(INFO_, "", 0, 0); */
|
||||
/*printf("Using %s %s on %s\n", getdata(INFO_MFR), getdata(INFO_MODEL), device_path);*/
|
||||
}
|
||||
|
||||
static const char *getpacket(int *we_know){
|
||||
fd_set readfds;
|
||||
struct timeval tv;
|
||||
int bytes_per_packet=0;
|
||||
int ret;
|
||||
static const char *packet_id=NULL;
|
||||
static char buf[256];
|
||||
const char *s;
|
||||
ssize_t r;
|
||||
|
||||
|
||||
bytes_per_packet=*we_know;
|
||||
D(printf("getpacket with %d\n",bytes_per_packet);)
|
||||
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(upsfd,&readfds);
|
||||
/* Wait up to 2 seconds. */
|
||||
tv.tv_sec = 5;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
ret=select(upsfd+1, &readfds, NULL, NULL, &tv);
|
||||
if (!ret) {
|
||||
s="Nothing received from UPS. Check cable conexion";
|
||||
upslogx(LOG_ERR, "%s", s);
|
||||
D(printf("%s\n",s);)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
r=read(upsfd,buf,255);
|
||||
D(printf("%d bytes read: ",r);)
|
||||
buf[r]=0;
|
||||
if (bytes_per_packet && r < bytes_per_packet){
|
||||
ssize_t rr;
|
||||
D(printf("short read...\n");)
|
||||
usleep(500000);
|
||||
tv.tv_sec = 2;
|
||||
tv.tv_usec = 0;
|
||||
ret=select(upsfd+1, &readfds, NULL, NULL, &tv);
|
||||
if (!ret) return NULL;
|
||||
rr=read(upsfd,buf+r,255-r);
|
||||
r += rr;
|
||||
if (r < bytes_per_packet) return NULL;
|
||||
}
|
||||
|
||||
if (!bytes_per_packet){ /* packet size determination */
|
||||
/* if (r%10 && r%9) {
|
||||
printf("disregarding incomplete packet\n");
|
||||
return NULL;
|
||||
}*/
|
||||
if (r%10==0) *we_know=10;
|
||||
else if (r%9==0) *we_know=9;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* by here we have bytes_per_packet and a complete packet */
|
||||
/* lets check if within the complete packet we have a valid packet */
|
||||
if (bytes_per_packet == 10) packet_id="&&&"; else packet_id="***";
|
||||
s=strstr(buf,packet_id);
|
||||
/* check validity of packet */
|
||||
if (!s) {
|
||||
s="isbmex: no valid packet signature!";
|
||||
upslogx(LOG_ERR, "%s", s);
|
||||
D(printf("%s\n",s);)
|
||||
*we_know=0;
|
||||
return NULL;
|
||||
}
|
||||
D(if (s != buf) printf("overlapping packet received\n");)
|
||||
if ((int) strlen(s) < bytes_per_packet) {
|
||||
D(printf("incomplete packet information\n");)
|
||||
return NULL;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
printf("Got signal:");
|
||||
{int i;for (i=0;i<strlen(s);i++) printf(" <%d>",(unsigned char)s[i]);}
|
||||
printf("\n");
|
||||
#endif
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
static float high_volt=-1, low_volt=999;
|
||||
const char *buf=NULL;
|
||||
char buf2[17];
|
||||
int i;
|
||||
static int bytes_per_packet=0;
|
||||
|
||||
for (i=0;i<5;i++) {
|
||||
if ((buf=getpacket(&bytes_per_packet)) != NULL) break;
|
||||
}
|
||||
if (!bytes_per_packet || !buf) {
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
|
||||
/* do the parsing */
|
||||
{
|
||||
float in_volt,battpct,acfreq;
|
||||
double d;
|
||||
D(printf("parsing (%d bytes per packet)\n",bytes_per_packet);)
|
||||
/* input voltage :*/
|
||||
if (bytes_per_packet==9) {
|
||||
in_volt = lagrange((unsigned char)buf[3]);
|
||||
} else {
|
||||
in_volt = interpol(sqrt((float)((unsigned char)buf[3]*256+(unsigned char)buf[4])));
|
||||
}
|
||||
snprintf(buf2,16,"%5.1f",in_volt);
|
||||
D(printf("utility=%s\n",buf2);)
|
||||
dstate_setinfo("input.voltage", "%s", buf2);
|
||||
|
||||
if (in_volt >= high_volt) high_volt=in_volt;
|
||||
snprintf(buf2,16,"%5.1f",high_volt);
|
||||
D(printf("highvolt=%s\n",buf2);)
|
||||
dstate_setinfo("input.voltage.maximum", "%s", buf2);
|
||||
|
||||
if (in_volt <= low_volt) low_volt=in_volt;
|
||||
snprintf(buf2,16,"%5.1f",low_volt);
|
||||
D(printf("lowvolt=%s\n",buf2);)
|
||||
dstate_setinfo("input.voltage.minimum", "%s", buf2);
|
||||
|
||||
battpct = ((double)((unsigned char)buf[(bytes_per_packet==10)?5:4])-168.0)*(100.0/(215.0-168.0));
|
||||
snprintf(buf2,16,"%5.1f",battpct);
|
||||
D(printf("battpct=%s\n",buf2);)
|
||||
dstate_setinfo("battery.charge", "%s", buf2);
|
||||
|
||||
d=(unsigned char)buf[(bytes_per_packet==10)?6:5]*256
|
||||
+ (unsigned char)buf[(bytes_per_packet==10)?7:6];
|
||||
acfreq = 1000000/d;
|
||||
snprintf(buf2,16,"%5.2f",acfreq);
|
||||
D(printf("acfreq=%s\n",buf2);)
|
||||
dstate_setinfo("input.frequency", "%s", buf2);
|
||||
|
||||
D(printf("status: ");)
|
||||
status_init();
|
||||
switch (buf[(bytes_per_packet==10)?8:7]){
|
||||
case 48: break; /* normal operation */
|
||||
case 49: D(printf("BOOST ");)
|
||||
status_set("BOOST");
|
||||
break;
|
||||
case 50: D(printf("TRIM ");)
|
||||
status_set("TRIM");
|
||||
default: break;
|
||||
}
|
||||
switch (buf[(bytes_per_packet==10)?9:8]){
|
||||
case 48: D(printf("OL ");)
|
||||
status_set("OL");
|
||||
break;
|
||||
case 50: D(printf("LB ");)
|
||||
status_set("LB");
|
||||
case 49: D(printf("OB ");)
|
||||
status_set("OB");
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
D(printf("\n");)
|
||||
status_commit();
|
||||
|
||||
|
||||
}
|
||||
dstate_dataok();
|
||||
return;
|
||||
}
|
||||
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
/* shutdown is supported on models with
|
||||
* contact closure. Some ISB models with serial
|
||||
* support support contact closure, some don't.
|
||||
* If yours does support it, then a 12V signal
|
||||
* on pin 9 does the trick (only when ups is
|
||||
* on OB condition) */
|
||||
/*
|
||||
* here try to do the pin 9 trick, if it does not
|
||||
* work, else:*/
|
||||
/* fatalx(EXIT_FAILURE, "Shutdown only supported with the Generic Driver, type 6 and special cable"); */
|
||||
/*fatalx(EXIT_FAILURE, "shutdown not supported");*/
|
||||
int i, ret;
|
||||
for(i=0;i<=5;i++)
|
||||
{
|
||||
ret = ser_send_char(upsfd, '#');
|
||||
usleep(50000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
}
|
||||
|
||||
/* list flags and values that you want to receive via -x */
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
}
|
||||
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
upsfd = ser_open(device_path);
|
||||
ser_set_speed(upsfd, device_path, B9600);
|
||||
}
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
ser_close(upsfd, device_path);
|
||||
}
|
||||
259
drivers/ivtscd.c
Normal file
259
drivers/ivtscd.c
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
/*
|
||||
* ivtscd.c - model specific routines for the IVT Solar Controller driver
|
||||
*
|
||||
* Copyright (C) 2009 Arjen de Korte <adkorte-guest@alioth.debian.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "serial.h"
|
||||
|
||||
#define DRIVER_NAME "IVT Solar Controller driver"
|
||||
#define DRIVER_VERSION "0.02"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Arjen de Korte <adkorte-guest@alioth.debian.org>",
|
||||
DRV_EXPERIMENTAL,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static struct {
|
||||
struct {
|
||||
float act;
|
||||
float low;
|
||||
float min;
|
||||
float nom;
|
||||
float max;
|
||||
} voltage;
|
||||
struct {
|
||||
float min;
|
||||
float act;
|
||||
float max;
|
||||
} current;
|
||||
float temperature;
|
||||
} battery;
|
||||
|
||||
static int ivt_status()
|
||||
{
|
||||
char reply[SMALLBUF];
|
||||
int ret, i, j = 0;
|
||||
|
||||
ser_flush_io(upsfd);
|
||||
|
||||
/*
|
||||
* send: F\n
|
||||
*/
|
||||
ret = ser_send(upsfd, "F");
|
||||
|
||||
if (ret < 0) {
|
||||
upsdebug_with_errno(3, "send");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
upsdebug_with_errno(3, "send: timeout");
|
||||
return -1;
|
||||
}
|
||||
|
||||
upsdebugx(3, "send: F");
|
||||
sleep(1); /* allow controller some time to digest this */
|
||||
|
||||
/*
|
||||
* read: R:12,57;- 1,1;20;12,57;13,18;- 2,1; 1,5;\n
|
||||
*/
|
||||
ret = ser_get_buf(upsfd, reply, sizeof(reply), 1, 0);
|
||||
|
||||
if (ret < 0) {
|
||||
upsdebug_with_errno(3, "read");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
upsdebugx(3, "read: timeout");
|
||||
return -1;
|
||||
}
|
||||
|
||||
upsdebugx(3, "read: %.*s", (int)strcspn(reply, "\r\n"), reply);
|
||||
upsdebug_hex(4, " \\_", reply, ret);
|
||||
|
||||
for (i = 0; i < ret; i++) {
|
||||
switch(reply[i])
|
||||
{
|
||||
case ',': /* convert ',' to '.' */
|
||||
reply[j++] = '.';
|
||||
break;
|
||||
case ' ': /* skip over white space */
|
||||
case '\0': /* skip over null characters */
|
||||
break;
|
||||
default: /* leave the rest as is */
|
||||
reply[j++] = reply[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
reply[j++] = '\0';
|
||||
|
||||
ret = sscanf(reply, "R:%f;%f;%f;%f;%f;%f;%f;", &battery.voltage.act, &battery.current.act, &battery.temperature,
|
||||
&battery.voltage.min, &battery.voltage.max, &battery.current.min, &battery.current.max);
|
||||
|
||||
upsdebugx(3, "Parsed %d parameters from reply", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int instcmd(const char *cmdname, const char *extra)
|
||||
{
|
||||
if (!strcasecmp(cmdname, "reset.input.minmax")) {
|
||||
ser_send(upsfd, "L");
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
}
|
||||
|
||||
void upsdrv_initinfo(void)
|
||||
{
|
||||
if (ivt_status() < 7) {
|
||||
fatal_with_errno(EXIT_FAILURE, "IVT Solar Controller not detected");
|
||||
}
|
||||
|
||||
/* set the device general information */
|
||||
dstate_setinfo("device.mfr", "IVT");
|
||||
dstate_setinfo("device.model", "Solar Controller Device");
|
||||
dstate_setinfo("device.type", "scd");
|
||||
|
||||
dstate_addcmd("reset.input.minmax");
|
||||
|
||||
upsh.instcmd = instcmd;
|
||||
}
|
||||
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
if (ivt_status() < 7) {
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
|
||||
dstate_setinfo("battery.voltage", "%.2f", battery.voltage.act);
|
||||
dstate_setinfo("battery.voltage.minimum", "%.2f", battery.voltage.min);
|
||||
dstate_setinfo("battery.voltage.maximum", "%.2f", battery.voltage.max);
|
||||
|
||||
dstate_setinfo("battery.current", "%.1f", battery.current.act);
|
||||
dstate_setinfo("battery.current.minimum", "%.1f", battery.current.min);
|
||||
dstate_setinfo("battery.current.maximum", "%.1f", battery.current.max);
|
||||
|
||||
dstate_setinfo("battery.temperature", "%.0f", battery.temperature);
|
||||
|
||||
status_init();
|
||||
|
||||
if (battery.current.act > 0) {
|
||||
status_set("OL"); /* charging */
|
||||
} else {
|
||||
status_set("OB"); /* discharging */
|
||||
}
|
||||
|
||||
if (battery.voltage.act < battery.voltage.low) {
|
||||
status_set("LB");
|
||||
}
|
||||
|
||||
status_commit();
|
||||
|
||||
dstate_dataok();
|
||||
}
|
||||
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
while (1) {
|
||||
|
||||
if (ivt_status() < 7) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (battery.voltage.act < battery.voltage.nom) {
|
||||
continue;
|
||||
}
|
||||
|
||||
fatalx(EXIT_SUCCESS, "Power is back!");
|
||||
}
|
||||
}
|
||||
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
}
|
||||
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
}
|
||||
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
struct termios tio;
|
||||
const char *val;
|
||||
|
||||
upsfd = ser_open(device_path);
|
||||
ser_set_speed(upsfd, device_path, B1200);
|
||||
|
||||
if (tcgetattr(upsfd, &tio)) {
|
||||
fatal_with_errno(EXIT_FAILURE, "tcgetattr");
|
||||
}
|
||||
|
||||
/*
|
||||
* Use canonical mode input processing (to read reply line)
|
||||
*/
|
||||
tio.c_lflag |= ICANON; /* Canonical input (erase and kill processing) */
|
||||
tio.c_iflag |= IGNCR; /* Ignore CR */
|
||||
tio.c_iflag |= IGNBRK; /* Ignore break condition */
|
||||
tio.c_oflag |= ONLCR; /* Map NL to CR-NL on output */
|
||||
|
||||
tio.c_cc[VEOF] = _POSIX_VDISABLE;
|
||||
tio.c_cc[VEOL] = _POSIX_VDISABLE;
|
||||
tio.c_cc[VERASE] = _POSIX_VDISABLE;
|
||||
tio.c_cc[VINTR] = _POSIX_VDISABLE;
|
||||
tio.c_cc[VKILL] = _POSIX_VDISABLE;
|
||||
tio.c_cc[VQUIT] = _POSIX_VDISABLE;
|
||||
tio.c_cc[VSUSP] = _POSIX_VDISABLE;
|
||||
tio.c_cc[VSTART] = _POSIX_VDISABLE;
|
||||
tio.c_cc[VSTOP] = _POSIX_VDISABLE;
|
||||
|
||||
if (tcsetattr(upsfd, TCSANOW, &tio)) {
|
||||
fatal_with_errno(EXIT_FAILURE, "tcsetattr");
|
||||
}
|
||||
|
||||
/*
|
||||
* Set DTR and clear RTS to provide power for the serial interface.
|
||||
*/
|
||||
ser_set_dtr(upsfd, 1);
|
||||
ser_set_rts(upsfd, 0);
|
||||
|
||||
val = dstate_getinfo("battery.voltage.nominal");
|
||||
battery.voltage.nom = (val) ? strtod(val, NULL) : 12.00;
|
||||
|
||||
val = dstate_getinfo("battery.voltage.low");
|
||||
battery.voltage.low = (val) ? strtod(val, NULL) : 10.80;
|
||||
|
||||
if (battery.voltage.nom <= battery.voltage.low) {
|
||||
fatalx(EXIT_FAILURE, "Nominal battery voltage must be higher than low battery voltage!");
|
||||
}
|
||||
}
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
ser_set_dtr(upsfd, 0);
|
||||
ser_close(upsfd, device_path);
|
||||
}
|
||||
949
drivers/libhid.c
Normal file
949
drivers/libhid.c
Normal file
|
|
@ -0,0 +1,949 @@
|
|||
/*!
|
||||
* @file libhid.c
|
||||
* @brief HID Library - User API (Generic HID Access using MGE HIDParser)
|
||||
*
|
||||
* @author Copyright (C) 2003 - 2007
|
||||
* Arnaud Quette <arnaud.quette@free.fr> && <arnaud.quette@mgeups.com>
|
||||
* John Stamp <kinsayder@hotmail.com>
|
||||
* Peter Selinger <selinger@users.sourceforge.net>
|
||||
* Arjen de Korte <adkorte-guest@alioth.debian.org>
|
||||
*
|
||||
* This program is sponsored by MGE UPS SYSTEMS - opensource.mgeups.com
|
||||
*
|
||||
* The logic of this file is ripped from mge-shut driver (also from
|
||||
* Arnaud Quette), which is a "HID over serial link" UPS driver for
|
||||
* Network UPS Tools <http://www.networkupstools.org/>
|
||||
*
|
||||
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
/* #include <math.h> */
|
||||
#include "libhid.h"
|
||||
#include "hidparser.h"
|
||||
#include "common.h" /* for xmalloc, upsdebugx prototypes */
|
||||
|
||||
/* Communication layers and drivers (USB and MGE SHUT) */
|
||||
#ifdef SHUT_MODE
|
||||
#include "libshut.h"
|
||||
communication_subdriver_t *comm_driver = &shut_subdriver;
|
||||
#else
|
||||
#include "libusb.h"
|
||||
communication_subdriver_t *comm_driver = &usb_subdriver;
|
||||
#endif
|
||||
|
||||
/* support functions */
|
||||
static double logical_to_physical(HIDData_t *Data, long logical);
|
||||
static long physical_to_logical(HIDData_t *Data, double physical);
|
||||
static const char *hid_lookup_path(const HIDNode_t usage, usage_tables_t *utab);
|
||||
static long hid_lookup_usage(const char *name, usage_tables_t *utab);
|
||||
static int string_to_path(const char *string, HIDPath_t *path, usage_tables_t *utab);
|
||||
static int path_to_string(char *string, size_t size, const HIDPath_t *path, usage_tables_t *utab);
|
||||
static int8_t get_unit_expo(const HIDData_t *hiddata);
|
||||
static double exponent(double a, int8_t b);
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* report buffering system */
|
||||
|
||||
/* HID data items are retrieved via "reports". Each report is
|
||||
identified by a report ID, which is an integer in the range
|
||||
0-255. Each report can hold several items. To avoid retrieving a
|
||||
given report multiple times in short succession, we use a data
|
||||
structure called a "report buffer". The functions in this group
|
||||
operate on entire *reports*, not individual data items. */
|
||||
|
||||
void free_report_buffer(reportbuf_t *rbuf)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!rbuf)
|
||||
return;
|
||||
|
||||
for (i=0; i<256; i++) {
|
||||
free(rbuf->data[i]);
|
||||
}
|
||||
|
||||
free(rbuf);
|
||||
}
|
||||
|
||||
/* allocate a new report buffer. Return pointer on success, else NULL
|
||||
with errno set. The returned data structure must later be freed
|
||||
with free_report_buffer(). */
|
||||
reportbuf_t *new_report_buffer(HIDDesc_t *pDesc)
|
||||
{
|
||||
HIDData_t *pData;
|
||||
reportbuf_t *rbuf;
|
||||
int i, id;
|
||||
|
||||
if (!pDesc)
|
||||
return NULL;
|
||||
|
||||
rbuf = calloc(1, sizeof(*rbuf));
|
||||
if (!rbuf) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* now go through all items that are part of this report */
|
||||
for (i=0; i<pDesc->nitems; i++) {
|
||||
|
||||
pData = &pDesc->item[i];
|
||||
|
||||
id = pData->ReportID;
|
||||
|
||||
/* skip reports that already have been allocated */
|
||||
if (rbuf->data[id])
|
||||
continue;
|
||||
|
||||
/* first byte holds id */
|
||||
rbuf->len[id] = pDesc->replen[id] + 1;
|
||||
|
||||
/* skip zero length reports */
|
||||
if (rbuf->len[id] < 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
rbuf->data[id] = calloc(rbuf->len[id], sizeof(*(rbuf->data[id])));
|
||||
if (rbuf->data[id])
|
||||
continue;
|
||||
|
||||
/* on failure, give up what we got so far */
|
||||
free_report_buffer(rbuf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return rbuf;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* the functions in this next group operate on buffered reports, but
|
||||
operate on individual items, not whole reports. */
|
||||
|
||||
/* refresh the report with the given id in the report buffer rbuf. If
|
||||
the report is not yet in the buffer, or if it is older than "age"
|
||||
seconds, then the report is freshly read from the USB
|
||||
device. Otherwise, it is unchanged.
|
||||
Return 0 on success, -1 on error with errno set. */
|
||||
static int refresh_report_buffer(reportbuf_t *rbuf, hid_dev_handle_t udev, HIDData_t *pData, int age)
|
||||
{
|
||||
int id = pData->ReportID;
|
||||
int r;
|
||||
unsigned char buf[SMALLBUF];
|
||||
|
||||
if (rbuf->ts[id] + age > time(NULL)) {
|
||||
/* buffered report is still good; nothing to do */
|
||||
upsdebug_hex(3, "Report[buf]", rbuf->data[id], rbuf->len[id]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = comm_driver->get_report(udev, id, buf, sizeof(buf));
|
||||
if (r <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* broken report descriptors are common, so store whatever we can */
|
||||
memcpy(rbuf->data[id], buf, rbuf->len[id]);
|
||||
|
||||
if (rbuf->len[id] != r) {
|
||||
upsdebugx(2, "%s: expected %d bytes, but got %d instead", __func__, rbuf->len[id], r);
|
||||
upsdebug_hex(3, "Report[err]", buf, r);
|
||||
} else {
|
||||
upsdebug_hex(3, "Report[get]", rbuf->data[id], rbuf->len[id]);
|
||||
}
|
||||
|
||||
/* have (valid) report */
|
||||
time(&rbuf->ts[id]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* read the logical value for the given pData. No logical to physical
|
||||
conversion is performed. If age>0, the read operation is buffered
|
||||
if the item's age is less than "age". On success, return 0 and
|
||||
store the answer in *value. On failure, return -1 and set errno. */
|
||||
static int get_item_buffered(reportbuf_t *rbuf, hid_dev_handle_t udev, HIDData_t *pData, long *Value, int age)
|
||||
{
|
||||
int id = pData->ReportID;
|
||||
int r;
|
||||
|
||||
r = refresh_report_buffer(rbuf, udev, pData, age);
|
||||
if (r<0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
GetValue(rbuf->data[id], pData, Value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* set the logical value for the given pData. No physical to logical
|
||||
conversion is performed. On success, return 0, and failure, return
|
||||
-1 and set errno. The updated value is sent to the device. */
|
||||
static int set_item_buffered(reportbuf_t *rbuf, hid_dev_handle_t udev, HIDData_t *pData, long Value)
|
||||
{
|
||||
int id = pData->ReportID;
|
||||
int r;
|
||||
|
||||
SetValue(pData, rbuf->data[id], Value);
|
||||
|
||||
r = comm_driver->set_report(udev, id, rbuf->data[id], rbuf->len[id]);
|
||||
if (r <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
upsdebug_hex(3, "Report[set]", rbuf->data[id], rbuf->len[id]);
|
||||
|
||||
/* expire report */
|
||||
rbuf->ts[id] = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* file a given report in the report buffer. This is used when the
|
||||
report has been obtained without having been explicitly requested,
|
||||
e.g., it arrived through an interrupt transfer. Returns 0 on
|
||||
success, -1 on error with errno set. */
|
||||
static int file_report_buffer(reportbuf_t *rbuf, unsigned char *buf, int buflen)
|
||||
{
|
||||
int id = buf[0];
|
||||
|
||||
/* broken report descriptors are common, so store whatever we can */
|
||||
memcpy(rbuf->data[id], buf, rbuf->len[id]);
|
||||
|
||||
if (rbuf->len[id] != buflen) {
|
||||
upsdebugx(2, "%s: expected %d bytes, but got %d instead", __func__, rbuf->len[id], buflen);
|
||||
upsdebug_hex(3, "Report[err]", buf, buflen);
|
||||
} else {
|
||||
upsdebug_hex(3, "Report[int]", rbuf->data[id], rbuf->len[id]);
|
||||
}
|
||||
|
||||
/* have (valid) report */
|
||||
time(&rbuf->ts[id]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/* Units and exponents table (HID PDC, 3.2.3) */
|
||||
#define NB_HID_UNITS 10
|
||||
static struct {
|
||||
const long Type;
|
||||
const int8_t Expo;
|
||||
} HIDUnits[NB_HID_UNITS] = {
|
||||
{ 0x00000000, 0 }, /* None */
|
||||
{ 0x00F0D121, 7 }, /* Voltage */
|
||||
{ 0x00100001, 0 }, /* Ampere */
|
||||
{ 0x0000D121, 7 }, /* VA */
|
||||
{ 0x0000D121, 7 }, /* Watts */
|
||||
{ 0x00001001, 0 }, /* second */
|
||||
{ 0x00010001, 0 }, /* K */
|
||||
{ 0x00000000, 0 }, /* percent */
|
||||
{ 0x0000F001, 0 }, /* Hertz */
|
||||
{ 0x00101001, 0 }, /* As */
|
||||
};
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/* CAUTION: be careful when modifying the output format of this function,
|
||||
* since it's used to produce sub-drivers "stub" using
|
||||
* scripts/subdriver/path-to-subdriver.sh
|
||||
*/
|
||||
void HIDDumpTree(hid_dev_handle_t udev, usage_tables_t *utab)
|
||||
{
|
||||
int i;
|
||||
#ifndef SHUT_MODE
|
||||
/* extract the VendorId for further testing */
|
||||
int vendorID = usb_device((struct usb_dev_handle *)udev)->descriptor.idVendor;
|
||||
#endif
|
||||
|
||||
/* Do not go further if we already know nothing will be displayed.
|
||||
* Some reports take a while before they timeout, so if these are
|
||||
* not used in the driver, they should only be fetched when we're
|
||||
* in debug mode
|
||||
*/
|
||||
if (nut_debug_level < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < pDesc->nitems; i++)
|
||||
{
|
||||
double value;
|
||||
HIDData_t *pData = &pDesc->item[i];
|
||||
|
||||
/* skip reports 254/255 for Eaton / MGE / Dell due to special handling needs */
|
||||
#ifdef SHUT_MODE
|
||||
if ((pData->ReportID == 254) || (pData->ReportID == 255)) {
|
||||
continue;
|
||||
}
|
||||
#else
|
||||
if ((vendorID == 0x0463) || (vendorID == 0x047c)) {
|
||||
if ((pData->ReportID == 254) || (pData->ReportID == 255)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Get data value */
|
||||
if (HIDGetDataValue(udev, pData, &value, MAX_TS) == 1) {
|
||||
upsdebugx(1, "Path: %s, Type: %s, ReportID: 0x%02x, Offset: %i, Size: %i, Value: %f",
|
||||
HIDGetDataItem(pData, utab), HIDDataType(pData), pData->ReportID, pData->Offset, pData->Size, value);
|
||||
continue;
|
||||
}
|
||||
|
||||
upsdebugx(1, "Path: %s, Type: %s, ReportID: 0x%02x, Offset: %i, Size: %i",
|
||||
HIDGetDataItem(pData, utab), HIDDataType(pData), pData->ReportID, pData->Offset, pData->Size);
|
||||
}
|
||||
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
/* Returns text string which can be used to display type of data
|
||||
*/
|
||||
const char *HIDDataType(const HIDData_t *hiddata)
|
||||
{
|
||||
switch (hiddata->Type)
|
||||
{
|
||||
case ITEM_FEATURE:
|
||||
return "Feature";
|
||||
case ITEM_INPUT:
|
||||
return "Input";
|
||||
case ITEM_OUTPUT:
|
||||
return "Output";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns pointer to the corresponding HIDData_t item
|
||||
* or NULL if path is not found in report descriptor
|
||||
*/
|
||||
HIDData_t *HIDGetItemData(const char *hidpath, usage_tables_t *utab)
|
||||
{
|
||||
int r;
|
||||
HIDPath_t Path;
|
||||
|
||||
r = string_to_path(hidpath, &Path, utab);
|
||||
if (r <= 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Get info on object (reportID, offset and size) */
|
||||
return FindObject_with_Path(pDesc, &Path, ITEM_FEATURE);
|
||||
}
|
||||
|
||||
char *HIDGetDataItem(const HIDData_t *hiddata, usage_tables_t *utab)
|
||||
{
|
||||
/* TODO: not thread safe! */
|
||||
static char itemPath[128];
|
||||
|
||||
path_to_string(itemPath, sizeof(itemPath), &hiddata->Path, utab);
|
||||
|
||||
return itemPath;
|
||||
}
|
||||
|
||||
/* Return the physical value associated with the given HIDData path.
|
||||
* return 1 if OK, 0 on fail, -errno otherwise (ie disconnect).
|
||||
*/
|
||||
int HIDGetDataValue(hid_dev_handle_t udev, HIDData_t *hiddata, double *Value, int age)
|
||||
{
|
||||
int r;
|
||||
long hValue;
|
||||
|
||||
if (hiddata == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = get_item_buffered(reportbuf, udev, hiddata, &hValue, age);
|
||||
if (r<0) {
|
||||
upsdebug_with_errno(1, "Can't retrieve Report %02x", hiddata->ReportID);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
*Value = hValue;
|
||||
|
||||
/* Convert Logical Min, Max and Value into Physical */
|
||||
*Value = logical_to_physical(hiddata, hValue);
|
||||
|
||||
/* Process exponents and units */
|
||||
*Value *= exponent(10, get_unit_expo(hiddata));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Return the physical value associated with the given path.
|
||||
* return 1 if OK, 0 on fail, -errno otherwise (ie disconnect).
|
||||
*/
|
||||
int HIDGetItemValue(hid_dev_handle_t udev, const char *hidpath, double *Value, usage_tables_t *utab)
|
||||
{
|
||||
return HIDGetDataValue(udev, HIDGetItemData(hidpath, utab), Value, MAX_TS);
|
||||
}
|
||||
|
||||
/* Return pointer to indexed string (empty if not found)
|
||||
*/
|
||||
char *HIDGetIndexString(hid_dev_handle_t udev, const int Index, char *buf, size_t buflen)
|
||||
{
|
||||
if (comm_driver->get_string(udev, Index, buf, buflen) < 1)
|
||||
buf[0] = '\0';
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* Return pointer to indexed string from HID path (empty if not found)
|
||||
*/
|
||||
char *HIDGetItemString(hid_dev_handle_t udev, const char *hidpath, char *buf, size_t buflen, usage_tables_t *utab)
|
||||
{
|
||||
double Index;
|
||||
|
||||
if (HIDGetDataValue(udev, HIDGetItemData(hidpath, utab), &Index, MAX_TS) != 1) {
|
||||
buf[0] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
return HIDGetIndexString(udev, Index, buf, buflen);
|
||||
}
|
||||
|
||||
/* Set the given physical value for the variable associated with
|
||||
* path. return 1 if OK, 0 on fail, -errno otherwise (ie disconnect).
|
||||
*/
|
||||
int HIDSetDataValue(hid_dev_handle_t udev, HIDData_t *hiddata, double Value)
|
||||
{
|
||||
int i, r;
|
||||
long hValue;
|
||||
|
||||
if (hiddata == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Test if Item is settable */
|
||||
/* FIXME: not constant == volatile, but
|
||||
* it doesn't imply that it's RW! */
|
||||
if (hiddata->Attribute == ATTR_DATA_CST) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Process exponents and units */
|
||||
Value /= exponent(10, get_unit_expo(hiddata));
|
||||
|
||||
/* Convert Physical Min, Max and Value into Logical */
|
||||
hValue = physical_to_logical(hiddata, Value);
|
||||
|
||||
r = set_item_buffered(reportbuf, udev, hiddata, hValue);
|
||||
if (r<0) {
|
||||
upsdebug_with_errno(1, "Can't set Report %02x", hiddata->ReportID);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
/* flush the report buffer (data may have changed) */
|
||||
for (i=0; i<256; i++) {
|
||||
reportbuf->ts[i] = 0;
|
||||
}
|
||||
|
||||
upsdebugx(4, "Set report succeeded");
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool_t HIDSetItemValue(hid_dev_handle_t udev, const char *hidpath, double Value, usage_tables_t *utab)
|
||||
{
|
||||
if (HIDSetDataValue(udev, HIDGetItemData(hidpath, utab), Value) != 1)
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* On success, return item count >0. When no notifications are available,
|
||||
* return 'error' or 'no event' code.
|
||||
*/
|
||||
int HIDGetEvents(hid_dev_handle_t udev, HIDData_t **event, int eventsize)
|
||||
{
|
||||
unsigned char buf[SMALLBUF];
|
||||
int itemCount = 0;
|
||||
int buflen, r, i;
|
||||
HIDData_t *pData;
|
||||
|
||||
/* needs libusb-0.1.8 to work => use ifdef and autoconf */
|
||||
buflen = comm_driver->get_interrupt(udev, buf, sizeof(buf), 250);
|
||||
if (buflen <= 0) {
|
||||
return buflen; /* propagate "error" or "no event" code */
|
||||
}
|
||||
|
||||
r = file_report_buffer(reportbuf, buf, buflen);
|
||||
if (r < 0) {
|
||||
upsdebug_with_errno(1, "%s: failed to buffer report", __func__);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
/* now read all items that are part of this report */
|
||||
for (i=0; i<pDesc->nitems; i++) {
|
||||
|
||||
pData = &pDesc->item[i];
|
||||
|
||||
/* Variable not part of this report */
|
||||
if (pData->ReportID != buf[0])
|
||||
continue;
|
||||
|
||||
/* Not an input report */
|
||||
if (pData->Type != ITEM_INPUT)
|
||||
continue;
|
||||
|
||||
/* maximum number of events reached? */
|
||||
if (itemCount >= eventsize) {
|
||||
upsdebugx(1, "%s: too many events (truncated)", __func__);
|
||||
break;
|
||||
}
|
||||
|
||||
event[itemCount++] = pData;
|
||||
}
|
||||
|
||||
if (itemCount == 0) {
|
||||
upsdebugx(1, "%s: unexpected input report (ignored)", __func__);
|
||||
}
|
||||
|
||||
return itemCount;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************
|
||||
* Support functions
|
||||
*******************************************************/
|
||||
|
||||
static double logical_to_physical(HIDData_t *Data, long logical)
|
||||
{
|
||||
double physical;
|
||||
double Factor;
|
||||
|
||||
upsdebugx(5, "PhyMax = %ld, PhyMin = %ld, LogMax = %ld, LogMin = %ld",
|
||||
Data->PhyMax, Data->PhyMin, Data->LogMax, Data->LogMin);
|
||||
|
||||
/* HID spec says that if one or both are undefined, or if they are
|
||||
* both 0, then PhyMin = LogMin, PhyMax = LogMax. */
|
||||
if (!Data->have_PhyMax || !Data->have_PhyMin ||
|
||||
(Data->PhyMax == 0 && Data->PhyMin == 0))
|
||||
{
|
||||
return (double)logical;
|
||||
}
|
||||
|
||||
/* Paranoia */
|
||||
if ((Data->PhyMax <= Data->PhyMin) || (Data->LogMax <= Data->LogMin))
|
||||
{
|
||||
/* this should not really happen */
|
||||
return (double)logical;
|
||||
}
|
||||
|
||||
Factor = (double)(Data->PhyMax - Data->PhyMin) / (Data->LogMax - Data->LogMin);
|
||||
/* Convert Value */
|
||||
physical = (double)((logical - Data->LogMin) * Factor) + Data->PhyMin;
|
||||
|
||||
if (physical > Data->PhyMax) {
|
||||
return Data->PhyMax;
|
||||
}
|
||||
|
||||
if (physical < Data->PhyMin) {
|
||||
return Data->PhyMin;
|
||||
}
|
||||
|
||||
return physical;
|
||||
}
|
||||
|
||||
static long physical_to_logical(HIDData_t *Data, double physical)
|
||||
{
|
||||
long logical;
|
||||
double Factor;
|
||||
|
||||
upsdebugx(5, "PhyMax = %ld, PhyMin = %ld, LogMax = %ld, LogMin = %ld",
|
||||
Data->PhyMax, Data->PhyMin, Data->LogMax, Data->LogMin);
|
||||
|
||||
/* HID spec says that if one or both are undefined, or if they are
|
||||
* both 0, then PhyMin = LogMin, PhyMax = LogMax. */
|
||||
if (!Data->have_PhyMax || !Data->have_PhyMin ||
|
||||
(Data->PhyMax == 0 && Data->PhyMin == 0))
|
||||
{
|
||||
return (long)physical;
|
||||
}
|
||||
|
||||
/* Paranoia */
|
||||
if ((Data->PhyMax <= Data->PhyMin) || (Data->LogMax <= Data->LogMin))
|
||||
{
|
||||
/* this should not really happen */
|
||||
return (long)physical;
|
||||
}
|
||||
|
||||
Factor = (double)(Data->LogMax - Data->LogMin) / (Data->PhyMax - Data->PhyMin);
|
||||
/* Convert Value */
|
||||
logical = (long)((physical - Data->PhyMin) * Factor) + Data->LogMin;
|
||||
|
||||
if (logical > Data->LogMax)
|
||||
return Data->LogMax;
|
||||
|
||||
if (logical < Data->LogMin)
|
||||
return Data->LogMin;
|
||||
|
||||
return logical;
|
||||
}
|
||||
|
||||
static int8_t get_unit_expo(const HIDData_t *hiddata)
|
||||
{
|
||||
int i;
|
||||
int8_t unit_expo = hiddata->UnitExp;
|
||||
|
||||
upsdebugx(5, "Unit = %08x, UnitExp = %d", (uint32_t)(hiddata->Unit), hiddata->UnitExp);
|
||||
|
||||
for (i = 0; i < NB_HID_UNITS; i++) {
|
||||
|
||||
if (HIDUnits[i].Type == hiddata->Unit) {
|
||||
unit_expo -= HIDUnits[i].Expo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
upsdebugx(5, "Exponent = %d", unit_expo);
|
||||
return unit_expo;
|
||||
}
|
||||
|
||||
/* exponent function: return a^b */
|
||||
static double exponent(double a, int8_t b)
|
||||
{
|
||||
if (b>0)
|
||||
return (a * exponent(a, --b)); /* a * a ... */
|
||||
|
||||
if (b<0)
|
||||
return ((1/a) * exponent(a, ++b)); /* (1/a) * (1/a) ... */
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* translate HID string path to numeric path and return path depth */
|
||||
static int string_to_path(const char *string, HIDPath_t *path, usage_tables_t *utab)
|
||||
{
|
||||
int i = 0;
|
||||
long usage;
|
||||
char buf[SMALLBUF];
|
||||
char *token, *last;
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s", string);
|
||||
|
||||
for (token = strtok_r(buf, ".", &last); token != NULL; token = strtok_r(NULL, ".", &last))
|
||||
{
|
||||
/* lookup tables first (to override defaults) */
|
||||
if ((usage = hid_lookup_usage(token, utab)) != -1)
|
||||
{
|
||||
path->Node[i++] = usage;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* translate unnamed path components such as "ff860024" */
|
||||
if (strlen(token) == strspn(token, "1234567890abcdefABCDEF"))
|
||||
{
|
||||
path->Node[i++] = strtol(token, NULL, 16);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* indexed collection */
|
||||
if (strlen(token) == strspn(token, "[1234567890]"))
|
||||
{
|
||||
path->Node[i++] = 0x00ff0000 + atoi(token+1);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Uh oh, typo in usage table? */
|
||||
upsdebugx(1, "string_to_path: couldn't parse %s from %s", token, string);
|
||||
}
|
||||
|
||||
path->Size = i;
|
||||
|
||||
upsdebugx(4, "string_to_path: depth = %d", path->Size);
|
||||
return i;
|
||||
}
|
||||
|
||||
/* translate HID numeric path to string path and return path depth */
|
||||
static int path_to_string(char *string, size_t size, const HIDPath_t *path, usage_tables_t *utab)
|
||||
{
|
||||
int i;
|
||||
const char *p;
|
||||
|
||||
snprintf(string, size, "%s", "");
|
||||
|
||||
for (i = 0; i < path->Size; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
snprintfcat(string, size, ".");
|
||||
|
||||
/* lookup tables first (to override defaults) */
|
||||
if ((p = hid_lookup_path(path->Node[i], utab)) != NULL)
|
||||
{
|
||||
snprintfcat(string, size, "%s", p);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* indexed collection */
|
||||
if ((path->Node[i] & 0xffff0000) == 0x00ff0000)
|
||||
{
|
||||
snprintfcat(string, size, "[%i]", path->Node[i] & 0x0000ffff);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* unnamed path components such as "ff860024" */
|
||||
snprintfcat(string, size, "%08x", path->Node[i]);
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/* usage conversion string -> numeric */
|
||||
static long hid_lookup_usage(const char *name, usage_tables_t *utab)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
for (i = 0; utab[i] != NULL; i++)
|
||||
{
|
||||
for (j = 0; utab[i][j].usage_name != NULL; j++)
|
||||
{
|
||||
if (strcasecmp(utab[i][j].usage_name, name))
|
||||
continue;
|
||||
|
||||
upsdebugx(5, "hid_lookup_usage: %s -> %08x", name, (unsigned int)utab[i][j].usage_code);
|
||||
return utab[i][j].usage_code;
|
||||
}
|
||||
}
|
||||
|
||||
upsdebugx(5, "hid_lookup_usage: %s -> not found in lookup table", name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* usage conversion numeric -> string */
|
||||
static const char *hid_lookup_path(const HIDNode_t usage, usage_tables_t *utab)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
for (i = 0; utab[i] != NULL; i++)
|
||||
{
|
||||
for (j = 0; utab[i][j].usage_name != NULL; j++)
|
||||
{
|
||||
if (utab[i][j].usage_code != usage)
|
||||
continue;
|
||||
|
||||
upsdebugx(5, "hid_lookup_path: %08x -> %s", (unsigned int)usage, utab[i][j].usage_name);
|
||||
return utab[i][j].usage_name;
|
||||
}
|
||||
}
|
||||
|
||||
upsdebugx(5, "hid_lookup_path: %08x -> not found in lookup table", (unsigned int)usage);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Lookup this usage name to find its code (page + index) */
|
||||
/* temporary usage code lookup */
|
||||
/* FIXME: put as external data, like in usb.ids (or use
|
||||
* this last?) */
|
||||
|
||||
/* Global usage table (from USB HID class definition) */
|
||||
usage_lkp_t hid_usage_lkp[] = {
|
||||
/* Power Device Page */
|
||||
{ "Undefined", 0x00840000 },
|
||||
{ "iName", 0x00840001 },
|
||||
{ "PresentStatus", 0x00840002 },
|
||||
{ "ChangedStatus", 0x00840003 },
|
||||
{ "UPS", 0x00840004 },
|
||||
{ "PowerSupply", 0x00840005 },
|
||||
/* 0x00840006-0x0084000f => Reserved */
|
||||
{ "BatterySystem", 0x00840010 },
|
||||
{ "BatterySystemID", 0x00840011 },
|
||||
{ "Battery", 0x00840012 },
|
||||
{ "BatteryID", 0x00840013 },
|
||||
{ "Charger", 0x00840014 },
|
||||
{ "ChargerID", 0x00840015 },
|
||||
{ "PowerConverter", 0x00840016 },
|
||||
{ "PowerConverterID", 0X00840017 },
|
||||
{ "OutletSystem", 0x00840018 },
|
||||
{ "OutletSystemID", 0x00840019 },
|
||||
{ "Input", 0x0084001a },
|
||||
{ "InputID", 0x0084001b },
|
||||
{ "Output", 0x0084001c },
|
||||
{ "OutputID", 0x0084001d },
|
||||
{ "Flow", 0x0084001e },
|
||||
{ "FlowID", 0x0084001f },
|
||||
{ "Outlet", 0x00840020 },
|
||||
{ "OutletID", 0x00840021 },
|
||||
{ "Gang", 0x00840022 },
|
||||
{ "GangID", 0x00840023 },
|
||||
{ "PowerSummary", 0x00840024 },
|
||||
{ "PowerSummaryID", 0x00840025 },
|
||||
/* 0x00840026-0x0084002f => Reserved */
|
||||
{ "Voltage", 0x00840030 },
|
||||
{ "Current", 0x00840031 },
|
||||
{ "Frequency", 0x00840032 },
|
||||
{ "ApparentPower", 0x00840033 },
|
||||
{ "ActivePower", 0x00840034 },
|
||||
{ "PercentLoad", 0x00840035 },
|
||||
{ "Temperature", 0x00840036 },
|
||||
{ "Humidity", 0x00840037 },
|
||||
{ "BadCount", 0x00840038 },
|
||||
/* 0x00840039-0x0084003f => Reserved */
|
||||
{ "ConfigVoltage", 0x00840040 },
|
||||
{ "ConfigCurrent", 0x00840041 },
|
||||
{ "ConfigFrequency", 0x00840042 },
|
||||
{ "ConfigApparentPower", 0x00840043 },
|
||||
{ "ConfigActivePower", 0x00840044 },
|
||||
{ "ConfigPercentLoad", 0x00840045 },
|
||||
{ "ConfigTemperature", 0x00840046 },
|
||||
{ "ConfigHumidity", 0x00840047 },
|
||||
/* 0x00840048-0x0084004f => Reserved */
|
||||
{ "SwitchOnControl", 0x00840050 },
|
||||
{ "SwitchOffControl", 0x00840051 },
|
||||
{ "ToggleControl", 0x00840052 },
|
||||
{ "LowVoltageTransfer", 0x00840053 },
|
||||
{ "HighVoltageTransfer", 0x00840054 },
|
||||
{ "DelayBeforeReboot", 0x00840055 },
|
||||
{ "DelayBeforeStartup", 0x00840056 },
|
||||
{ "DelayBeforeShutdown", 0x00840057 },
|
||||
{ "Test", 0x00840058 },
|
||||
{ "ModuleReset", 0x00840059 },
|
||||
{ "AudibleAlarmControl", 0x0084005a },
|
||||
/* 0x0084005b-0x0084005f => Reserved */
|
||||
{ "Present", 0x00840060 },
|
||||
{ "Good", 0x00840061 },
|
||||
{ "InternalFailure", 0x00840062 },
|
||||
{ "VoltageOutOfRange", 0x00840063 },
|
||||
{ "FrequencyOutOfRange", 0x00840064 },
|
||||
{ "Overload", 0x00840065 },
|
||||
/* Note: the correct spelling is "Overload", not "OverLoad",
|
||||
* according to the official specification, "Universal Serial
|
||||
* Bus Usage Tables for HID Power Devices", Release 1.0,
|
||||
* November 1, 1997 */
|
||||
{ "OverCharged", 0x00840066 },
|
||||
{ "OverTemperature", 0x00840067 },
|
||||
{ "ShutdownRequested", 0x00840068 },
|
||||
{ "ShutdownImminent", 0x00840069 },
|
||||
{ "SwitchOn/Off", 0x0084006b },
|
||||
{ "Switchable", 0x0084006c },
|
||||
{ "Used", 0x0084006d },
|
||||
{ "Boost", 0x0084006e },
|
||||
{ "Buck", 0x0084006f },
|
||||
{ "Initialized", 0x00840070 },
|
||||
{ "Tested", 0x00840071 },
|
||||
{ "AwaitingPower", 0x00840072 },
|
||||
{ "CommunicationLost", 0x00840073 },
|
||||
/* 0x00840074-0x008400fc => Reserved */
|
||||
{ "iManufacturer", 0x008400fd },
|
||||
{ "iProduct", 0x008400fe },
|
||||
{ "iSerialNumber", 0x008400ff },
|
||||
|
||||
/* Battery System Page */
|
||||
{ "Undefined", 0x00850000 },
|
||||
{ "SMBBatteryMode", 0x00850001 },
|
||||
{ "SMBBatteryStatus", 0x00850002 },
|
||||
{ "SMBAlarmWarning", 0x00850003 },
|
||||
{ "SMBChargerMode", 0x00850004 },
|
||||
{ "SMBChargerStatus", 0x00850005 },
|
||||
{ "SMBChargerSpecInfo", 0x00850006 },
|
||||
{ "SMBSelectorState", 0x00850007 },
|
||||
{ "SMBSelectorPresets", 0x00850008 },
|
||||
{ "SMBSelectorInfo", 0x00850009 },
|
||||
/* 0x0085000A-0x0085000f => Reserved */
|
||||
{ "OptionalMfgFunction1", 0x00850010 },
|
||||
{ "OptionalMfgFunction2", 0x00850011 },
|
||||
{ "OptionalMfgFunction3", 0x00850012 },
|
||||
{ "OptionalMfgFunction4", 0x00850013 },
|
||||
{ "OptionalMfgFunction5", 0x00850014 },
|
||||
{ "ConnectionToSMBus", 0x00850015 },
|
||||
{ "OutputConnection", 0x00850016 },
|
||||
{ "ChargerConnection", 0x00850017 },
|
||||
{ "BatteryInsertion", 0x00850018 },
|
||||
{ "Usenext", 0x00850019 },
|
||||
{ "OKToUse", 0x0085001a },
|
||||
{ "BatterySupported", 0x0085001b },
|
||||
{ "SelectorRevision", 0x0085001c },
|
||||
{ "ChargingIndicator", 0x0085001d },
|
||||
/* 0x0085001e-0x00850027 => Reserved */
|
||||
{ "ManufacturerAccess", 0x00850028 },
|
||||
{ "RemainingCapacityLimit", 0x00850029 },
|
||||
{ "RemainingTimeLimit", 0x0085002a },
|
||||
{ "AtRate", 0x0085002b },
|
||||
{ "CapacityMode", 0x0085002c },
|
||||
{ "BroadcastToCharger", 0x0085002d },
|
||||
{ "PrimaryBattery", 0x0085002e },
|
||||
{ "ChargeController", 0x0085002f },
|
||||
/* 0x00850030-0x0085003f => Reserved */
|
||||
{ "TerminateCharge", 0x00850040 },
|
||||
{ "TerminateDischarge", 0x00850041 },
|
||||
{ "BelowRemainingCapacityLimit", 0x00850042 },
|
||||
{ "RemainingTimeLimitExpired", 0x00850043 },
|
||||
{ "Charging", 0x00850044 },
|
||||
{ "Discharging", 0x00850045 },
|
||||
{ "FullyCharged", 0x00850046 },
|
||||
{ "FullyDischarged", 0x00850047 },
|
||||
{ "ConditioningFlag", 0x00850048 },
|
||||
{ "AtRateOK", 0x00850049 },
|
||||
{ "SMBErrorCode", 0x0085004a },
|
||||
{ "NeedReplacement", 0x0085004b },
|
||||
/* 0x0085004c-0x0085005f => Reserved */
|
||||
{ "AtRateTimeToFull", 0x00850060 },
|
||||
{ "AtRateTimeToEmpty", 0x00850061 },
|
||||
{ "AverageCurrent", 0x00850062 },
|
||||
{ "Maxerror", 0x00850063 },
|
||||
{ "RelativeStateOfCharge", 0x00850064 },
|
||||
{ "AbsoluteStateOfCharge", 0x00850065 },
|
||||
{ "RemainingCapacity", 0x00850066 },
|
||||
{ "FullChargeCapacity", 0x00850067 },
|
||||
{ "RunTimeToEmpty", 0x00850068 },
|
||||
{ "AverageTimeToEmpty", 0x00850069 },
|
||||
{ "AverageTimeToFull", 0x0085006a },
|
||||
{ "CycleCount", 0x0085006b },
|
||||
/* 0x0085006c-0x0085007f => Reserved */
|
||||
{ "BattPackModelLevel", 0x00850080 },
|
||||
{ "InternalChargeController", 0x00850081 },
|
||||
{ "PrimaryBatterySupport", 0x00850082 },
|
||||
{ "DesignCapacity", 0x00850083 },
|
||||
{ "SpecificationInfo", 0x00850084 },
|
||||
{ "ManufacturerDate", 0x00850085 },
|
||||
{ "SerialNumber", 0x00850086 },
|
||||
{ "iManufacturerName", 0x00850087 },
|
||||
{ "iDevicename", 0x00850088 }, /* sic! */
|
||||
{ "iDeviceChemistry", 0x00850089 }, /* misspelled as "iDeviceChemistery" in spec. */
|
||||
{ "ManufacturerData", 0x0085008a },
|
||||
{ "Rechargeable", 0x0085008b },
|
||||
{ "WarningCapacityLimit", 0x0085008c },
|
||||
{ "CapacityGranularity1", 0x0085008d },
|
||||
{ "CapacityGranularity2", 0x0085008e },
|
||||
{ "iOEMInformation", 0x0085008f },
|
||||
/* 0x00850090-0x008500bf => Reserved */
|
||||
{ "InhibitCharge", 0x008500c0 },
|
||||
{ "EnablePolling", 0x008500c1 },
|
||||
{ "ResetToZero", 0x008500c2 },
|
||||
/* 0x008500c3-0x008500cf => Reserved */
|
||||
{ "ACPresent", 0x008500d0 },
|
||||
{ "BatteryPresent", 0x008500d1 },
|
||||
{ "PowerFail", 0x008500d2 },
|
||||
{ "AlarmInhibited", 0x008500d3 },
|
||||
{ "ThermistorUnderRange", 0x008500d4 },
|
||||
{ "ThermistorHot", 0x008500d5 },
|
||||
{ "ThermistorCold", 0x008500d6 },
|
||||
{ "ThermistorOverRange", 0x008500d7 },
|
||||
{ "VoltageOutOfRange", 0x008500d8 },
|
||||
{ "CurrentOutOfRange", 0x008500d9 },
|
||||
{ "CurrentNotRegulated", 0x008500da },
|
||||
{ "VoltageNotRegulated", 0x008500db },
|
||||
{ "MasterMode", 0x008500dc },
|
||||
/* 0x008500dd-0x008500ef => Reserved */
|
||||
{ "ChargerSelectorSupport", 0x008500f0 },
|
||||
{ "ChargerSpec", 0x008500f1 },
|
||||
{ "Level2", 0x008500f2 },
|
||||
{ "Level3", 0x008500f3 },
|
||||
/* 0x008500f4-0x008500ff => Reserved */
|
||||
|
||||
/* end of structure. */
|
||||
{ NULL, 0 }
|
||||
};
|
||||
148
drivers/libhid.h
Normal file
148
drivers/libhid.h
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
/*!
|
||||
* @file libhid.h
|
||||
* @brief HID Library - User API
|
||||
*
|
||||
* @author Copyright (C) 2003 - 2007
|
||||
* Arnaud Quette <arnaud.quette@free.fr> && <arnaud.quette@mgeups.com>
|
||||
* Charles Lepple <clepple@ghz.cc>
|
||||
* Peter Selinger <selinger@users.sourceforge.net>
|
||||
* Arjen de Korte <adkorte-guest@alioth.debian.org>
|
||||
*
|
||||
* This program is sponsored by MGE UPS SYSTEMS - opensource.mgeups.com
|
||||
*
|
||||
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
#ifndef _LIBHID_H
|
||||
#define _LIBHID_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include "hidtypes.h"
|
||||
|
||||
#include "timehead.h"
|
||||
#ifdef SHUT_MODE
|
||||
#include "libshut.h"
|
||||
typedef SHUTDevice_t HIDDevice_t;
|
||||
typedef char HIDDeviceMatcher_t;
|
||||
typedef int hid_dev_handle_t;
|
||||
typedef shut_communication_subdriver_t communication_subdriver_t;
|
||||
#else
|
||||
#include "libusb.h"
|
||||
typedef USBDevice_t HIDDevice_t;
|
||||
typedef USBDeviceMatcher_t HIDDeviceMatcher_t;
|
||||
typedef usb_dev_handle * hid_dev_handle_t;
|
||||
typedef usb_communication_subdriver_t communication_subdriver_t;
|
||||
#endif
|
||||
|
||||
/* use explicit booleans */
|
||||
#ifndef FALSE
|
||||
typedef enum ebool { FALSE, TRUE } bool_t;
|
||||
#else
|
||||
typedef int bool_t;
|
||||
#endif
|
||||
|
||||
/* Device open modes */
|
||||
#define MODE_OPEN 0 /* open a HID device for the first time */
|
||||
#define MODE_REOPEN 1 /* reopen a HID device that was opened before */
|
||||
|
||||
#define MAX_TS 2 /* validity period of a gotten report (2 sec) */
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/* structure to describe an item in a usage table */
|
||||
typedef struct {
|
||||
const char *usage_name;
|
||||
const HIDNode_t usage_code;
|
||||
} usage_lkp_t;
|
||||
|
||||
extern usage_lkp_t hid_usage_lkp[];
|
||||
|
||||
/* an object of type usage_tables_t is a NULL-terminated array of
|
||||
* pointers to individual usage tables. */
|
||||
typedef usage_lkp_t *usage_tables_t;
|
||||
|
||||
extern communication_subdriver_t *comm_driver;
|
||||
extern HIDDesc_t *pDesc; /* parsed Report Descriptor */
|
||||
|
||||
/* report buffer structure: holds data about most recent report for
|
||||
each given report id */
|
||||
typedef struct reportbuf_s {
|
||||
time_t ts[256]; /* timestamp when report was retrieved */
|
||||
int len[256]; /* size of report data */
|
||||
unsigned char *data[256]; /* report data (allocated) */
|
||||
} reportbuf_t;
|
||||
|
||||
extern reportbuf_t *reportbuf; /* buffer for most recent reports */
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* HIDGetItemValue
|
||||
* -------------------------------------------------------------------------- */
|
||||
int HIDGetItemValue(hid_dev_handle_t udev, const char *hidpath, double *Value, usage_tables_t *utab);
|
||||
|
||||
/*
|
||||
* HIDGetItemString
|
||||
* -------------------------------------------------------------------------- */
|
||||
char *HIDGetItemString(hid_dev_handle_t udev, const char *hidpath, char *buf, size_t buflen, usage_tables_t *utab);
|
||||
|
||||
/*
|
||||
* HIDSetItemValue
|
||||
* -------------------------------------------------------------------------- */
|
||||
bool_t HIDSetItemValue(hid_dev_handle_t udev, const char *hidpath, double value, usage_tables_t *utab);
|
||||
|
||||
/*
|
||||
* GetItemData
|
||||
* -------------------------------------------------------------------------- */
|
||||
HIDData_t *HIDGetItemData(const char *hidpath, usage_tables_t *utab);
|
||||
|
||||
/*
|
||||
* GetDataItem
|
||||
* -------------------------------------------------------------------------- */
|
||||
char *HIDGetDataItem(const HIDData_t *hiddata, usage_tables_t *utab);
|
||||
|
||||
/*
|
||||
* HIDGetDataValue
|
||||
* -------------------------------------------------------------------------- */
|
||||
int HIDGetDataValue(hid_dev_handle_t udev, HIDData_t *hiddata, double *Value, int age);
|
||||
|
||||
/*
|
||||
* HIDSetDataValue
|
||||
* -------------------------------------------------------------------------- */
|
||||
int HIDSetDataValue(hid_dev_handle_t udev, HIDData_t *hiddata, double Value);
|
||||
|
||||
/*
|
||||
* HIDGetIndexString
|
||||
* -------------------------------------------------------------------------- */
|
||||
char *HIDGetIndexString(hid_dev_handle_t udev, int Index, char *buf, size_t buflen);
|
||||
|
||||
/*
|
||||
* HIDGetEvents
|
||||
* -------------------------------------------------------------------------- */
|
||||
int HIDGetEvents(hid_dev_handle_t udev, HIDData_t **event, int eventlen);
|
||||
|
||||
/*
|
||||
* Support functions
|
||||
* -------------------------------------------------------------------------- */
|
||||
void HIDDumpTree(hid_dev_handle_t udev, usage_tables_t *utab);
|
||||
const char *HIDDataType(const HIDData_t *hiddata);
|
||||
|
||||
void free_report_buffer(reportbuf_t *rbuf);
|
||||
reportbuf_t *new_report_buffer(HIDDesc_t *pDesc);
|
||||
|
||||
#endif /* _LIBHID_H */
|
||||
981
drivers/libshut.c
Normal file
981
drivers/libshut.c
Normal file
|
|
@ -0,0 +1,981 @@
|
|||
/*!
|
||||
* @file libshut.c
|
||||
* @brief HID Library - SHUT communication sub driver
|
||||
*
|
||||
* @author Copyright (C)
|
||||
* 2006 - 2009 Arnaud Quette <aquette.dev@gmail.com>
|
||||
*
|
||||
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* TODO list
|
||||
* - cleanup, cleanup, cleanup
|
||||
* - manage interrupt and complete libshut_get_interrupt / shut_control_msg routing
|
||||
* - baudrate negotiation
|
||||
* - complete shut_strerror
|
||||
* - validate / complete commands and data table in mge-hid from mge-shut
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "nut_stdint.h" /* for uint8_t, uint16_t, uint32_t */
|
||||
|
||||
#include "serial.h"
|
||||
#include "libshut.h"
|
||||
#include "common.h" /* for xmalloc, upsdebugx prototypes */
|
||||
|
||||
#define SHUT_DRIVER_NAME "SHUT communication driver"
|
||||
#define SHUT_DRIVER_VERSION "0.82"
|
||||
|
||||
/* communication driver description structure */
|
||||
upsdrv_info_t comm_upsdrv_info = {
|
||||
SHUT_DRIVER_NAME,
|
||||
SHUT_DRIVER_VERSION,
|
||||
NULL,
|
||||
0,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
#define MAX_TRY 4
|
||||
#define MAX_STRING_SIZE 128
|
||||
|
||||
/*!
|
||||
* HID descriptor, completed with desc{type,len}
|
||||
*/
|
||||
struct my_hid_descriptor {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint16_t bcdHID;
|
||||
uint8_t bCountryCode;
|
||||
uint8_t bNumDescriptors;
|
||||
uint8_t bReportDescriptorType;
|
||||
uint16_t wDescriptorLength;
|
||||
};
|
||||
|
||||
/*!********************************************************
|
||||
* USB spec information, synchronised with (ripped from) libusb
|
||||
*/
|
||||
|
||||
/*
|
||||
* Device and/or Interface Class codes
|
||||
*/
|
||||
#define USB_CLASS_PER_INTERFACE 0 /* for DeviceClass */
|
||||
#define USB_CLASS_AUDIO 1
|
||||
#define USB_CLASS_COMM 2
|
||||
#define USB_CLASS_HID 3
|
||||
#define USB_CLASS_PRINTER 7
|
||||
#define USB_CLASS_MASS_STORAGE 8
|
||||
#define USB_CLASS_HUB 9
|
||||
#define USB_CLASS_DATA 10
|
||||
#define USB_CLASS_VENDOR_SPEC 0xff
|
||||
|
||||
/*
|
||||
* Descriptor types
|
||||
*/
|
||||
#define USB_DT_DEVICE 0x01
|
||||
#define USB_DT_CONFIG 0x02
|
||||
#define USB_DT_STRING 0x03
|
||||
#define USB_DT_INTERFACE 0x04
|
||||
#define USB_DT_ENDPOINT 0x05
|
||||
|
||||
#define USB_DT_HID 0x21
|
||||
#define USB_DT_REPORT 0x22
|
||||
#define USB_DT_PHYSICAL 0x23
|
||||
#define USB_DT_HUB 0x29
|
||||
|
||||
/*
|
||||
* Descriptor sizes per descriptor type
|
||||
*/
|
||||
#define USB_DT_DEVICE_SIZE 18
|
||||
#define USB_DT_CONFIG_SIZE 9
|
||||
#define USB_DT_INTERFACE_SIZE 9
|
||||
#define USB_DT_ENDPOINT_SIZE 7
|
||||
#define USB_DT_ENDPOINT_AUDIO_SIZE 9 /* Audio extension */
|
||||
#define USB_DT_HUB_NONVAR_SIZE 7
|
||||
|
||||
/*
|
||||
* Standard requests
|
||||
*/
|
||||
#define USB_REQ_GET_STATUS 0x00
|
||||
#define USB_REQ_CLEAR_FEATURE 0x01
|
||||
/* 0x02 is reserved */
|
||||
#define USB_REQ_SET_FEATURE 0x03
|
||||
/* 0x04 is reserved */
|
||||
#define USB_REQ_SET_ADDRESS 0x05
|
||||
#define USB_REQ_GET_DESCRIPTOR 0x06
|
||||
#define USB_REQ_SET_DESCRIPTOR 0x07
|
||||
#define USB_REQ_GET_CONFIGURATION 0x08
|
||||
#define USB_REQ_SET_CONFIGURATION 0x09
|
||||
#define USB_REQ_GET_INTERFACE 0x0A
|
||||
#define USB_REQ_SET_INTERFACE 0x0B
|
||||
#define USB_REQ_SYNCH_FRAME 0x0C
|
||||
|
||||
#define USB_TYPE_STANDARD (0x00 << 5)
|
||||
#define USB_TYPE_CLASS (0x01 << 5)
|
||||
#define USB_TYPE_VENDOR (0x02 << 5)
|
||||
#define USB_TYPE_RESERVED (0x03 << 5)
|
||||
|
||||
#define USB_RECIP_DEVICE 0x00
|
||||
#define USB_RECIP_INTERFACE 0x01
|
||||
#define USB_RECIP_ENDPOINT 0x02
|
||||
#define USB_RECIP_OTHER 0x03
|
||||
|
||||
/*
|
||||
* Various libusb API related stuff
|
||||
*/
|
||||
#define USB_ENDPOINT_IN 0x80
|
||||
#define USB_ENDPOINT_OUT 0x00
|
||||
/*!
|
||||
* end of USB spec information
|
||||
*********************************************************/
|
||||
|
||||
/*!
|
||||
* HID definitions
|
||||
*/
|
||||
#define HID_REPORT_TYPE_INPUT 0x01
|
||||
#define HID_REPORT_TYPE_OUTPUT 0x02
|
||||
#define HID_REPORT_TYPE_FEATURE 0x03
|
||||
|
||||
#define REQUEST_TYPE_USB 0x80
|
||||
#define REQUEST_TYPE_HID 0x81
|
||||
#define REQUEST_TYPE_GET_REPORT 0xa1
|
||||
#define REQUEST_TYPE_SET_REPORT 0x21
|
||||
|
||||
#define MAX_REPORT_SIZE 0x1800
|
||||
|
||||
/*!
|
||||
* SHUT definitions - From Simplified SHUT spec
|
||||
*/
|
||||
|
||||
#define SHUT_TYPE_REQUEST 0x01
|
||||
#define SHUT_TYPE_RESPONSE 0x04
|
||||
#define SHUT_TYPE_NOTIFY 0x05
|
||||
#define SHUT_OK 0x06
|
||||
#define SHUT_NOK 0x15
|
||||
/* sync signals are also used to set the notification level */
|
||||
#define SHUT_SYNC 0x16 /* complete notifications - not yet managed */
|
||||
/* but needed for some early Ellipse models */
|
||||
#define SHUT_SYNC_LIGHT 0x17 /* partial notifications */
|
||||
#define SHUT_SYNC_OFF 0x18 /* disable notifications - only do polling */
|
||||
#define SHUT_PKT_LAST 0x80
|
||||
|
||||
#define SHUT_TIMEOUT 3000
|
||||
|
||||
/*!
|
||||
* SHUT functions for HID marshalling
|
||||
*/
|
||||
int shut_get_descriptor(int upsfd, unsigned char type,
|
||||
unsigned char index, void *buf, int size);
|
||||
int shut_get_string_simple(int upsfd, int index,
|
||||
char *buf, size_t buflen);
|
||||
int libshut_get_report(int upsfd, int ReportId,
|
||||
unsigned char *raw_buf, int ReportSize );
|
||||
int shut_set_report(int upsfd, int id, unsigned char *pkt, int reportlen);
|
||||
int libshut_get_interrupt(int upsfd, unsigned char *buf,
|
||||
int bufsize, int timeout);
|
||||
void libshut_close(int upsfd);
|
||||
|
||||
/* FIXME */
|
||||
char * shut_strerror() { return ""; }
|
||||
|
||||
/*!
|
||||
* From SHUT specifications
|
||||
* sync'ed with libusb
|
||||
*/
|
||||
|
||||
typedef struct shut_ctrltransfer_s {
|
||||
uint8_t bRequestType;
|
||||
uint8_t bRequest;
|
||||
uint16_t wValue;
|
||||
uint16_t wIndex;
|
||||
uint16_t wLength;
|
||||
|
||||
uint32_t timeout; /* in milliseconds */
|
||||
|
||||
/* pointer to data */
|
||||
void *data;
|
||||
/* uint8_t padding[8]; for use with shut_set_report?! */
|
||||
} shut_ctrltransfer_t;
|
||||
|
||||
|
||||
typedef union hid_data_t {
|
||||
shut_ctrltransfer_t hid_pkt;
|
||||
uint8_t raw_pkt[8]; /* max report lengh, was 8 */
|
||||
} hid_data_t;
|
||||
|
||||
typedef struct shut_packet_s {
|
||||
uint8_t bType;
|
||||
uint8_t bLength;
|
||||
hid_data_t data;
|
||||
uint8_t bChecksum;
|
||||
} shut_packet_t;
|
||||
|
||||
typedef union shut_data_t {
|
||||
shut_packet_t shut_pkt;
|
||||
uint8_t raw_pkt[11];
|
||||
} shut_data_t;
|
||||
|
||||
typedef union hid_desc_data_t {
|
||||
struct my_hid_descriptor hid_desc;
|
||||
uint8_t raw_desc[9]; /* max report lengh, aws 9 */
|
||||
} hid_desc_data_t;
|
||||
|
||||
/* Device descriptor */
|
||||
typedef struct device_descriptor_s {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint16_t bcdUSB;
|
||||
uint8_t bDeviceClass;
|
||||
uint8_t bDeviceSubClass;
|
||||
uint8_t bDeviceProtocol;
|
||||
uint8_t bMaxPacketSize0;
|
||||
uint16_t idVendor;
|
||||
uint16_t idProduct;
|
||||
uint16_t bcdDevice;
|
||||
uint8_t iManufacturer;
|
||||
uint8_t iProduct;
|
||||
uint8_t iSerialNumber;
|
||||
uint8_t bNumConfigurations;
|
||||
} device_descriptor_t;
|
||||
#if 0
|
||||
typedef union device_desc_data_t {
|
||||
device_descriptor_t dev_desc;
|
||||
uint8_t raw_desc[18];
|
||||
} device_desc_data_t;
|
||||
#endif
|
||||
/* Low level SHUT (Serial HID UPS Transfer) routines */
|
||||
void setline(int upsfd, int set);
|
||||
int shut_synchronise(int upsfd);
|
||||
int shut_wait_ack(int upsfd);
|
||||
int shut_interrupt_read(int upsfd, int ep, unsigned char *bytes,
|
||||
int size, int timeout);
|
||||
int shut_control_msg(int upsfd, int requesttype, int request, int value,
|
||||
int index, unsigned char *bytes, int size, int timeout);
|
||||
|
||||
/* Data portability */
|
||||
/* realign packet data according to Endianess */
|
||||
#define BYTESWAP(in) (((in & 0xFF) << 8) + ((in & 0xFF00) >> 8))
|
||||
static void align_request(struct shut_ctrltransfer_s *ctrl )
|
||||
{
|
||||
#if WORDS_BIGENDIAN
|
||||
/* Sparc/Mips/... are big endian, USB/SHUT little endian */
|
||||
(*ctrl).wValue = BYTESWAP((*ctrl).wValue);
|
||||
(*ctrl).wIndex = BYTESWAP((*ctrl).wIndex);
|
||||
(*ctrl).wLength = BYTESWAP((*ctrl).wLength);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* On success, fill in the curDevice structure and return the report
|
||||
* descriptor length. On failure, return -1.
|
||||
* Note: When callback is not NULL, the report descriptor will be
|
||||
* passed to this function together with the upsfd and SHUTDevice_t
|
||||
* information. This callback should return a value > 0 if the device
|
||||
* is accepted, or < 1 if not.
|
||||
*/
|
||||
int libshut_open(int *upsfd, SHUTDevice_t *curDevice, char *device_path,
|
||||
int (*callback)(int upsfd, SHUTDevice_t *hd, unsigned char *rdbuf, int rdlen))
|
||||
{
|
||||
int ret, res;
|
||||
unsigned char buf[20];
|
||||
char string[MAX_STRING_SIZE];
|
||||
struct my_hid_descriptor *desc;
|
||||
struct device_descriptor_s *dev_descriptor;
|
||||
|
||||
/* report descriptor */
|
||||
unsigned char rdbuf[MAX_REPORT_SIZE];
|
||||
int rdlen;
|
||||
|
||||
upsdebugx(2, "libshut_open: using port %s", device_path);
|
||||
|
||||
/* If device is still open, close it */
|
||||
if (*upsfd > 0) {
|
||||
ser_close(*upsfd, device_path);
|
||||
}
|
||||
|
||||
/* initialize serial port */
|
||||
/* FIXME: add variable baudrate detection */
|
||||
*upsfd = ser_open(device_path);
|
||||
ser_set_speed(*upsfd, device_path, B2400);
|
||||
setline(*upsfd, 1);
|
||||
|
||||
/* initialise communication */
|
||||
if (!shut_synchronise(*upsfd))
|
||||
{
|
||||
upsdebugx(2, "No communication with UPS");
|
||||
return -1;
|
||||
}
|
||||
|
||||
upsdebugx(2, "Communication with UPS established");
|
||||
|
||||
/* we can skip the rest due to serial bus specifics! */
|
||||
if (!callback) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Get DEVICE descriptor */
|
||||
dev_descriptor = (struct device_descriptor_s *)buf;
|
||||
res = shut_get_descriptor(*upsfd, USB_DT_DEVICE, 0, buf, USB_DT_DEVICE_SIZE);
|
||||
/* res = shut_control_msg(devp, USB_ENDPOINT_IN+1, USB_REQ_GET_DESCRIPTOR,
|
||||
(USB_DT_DEVICE << 8) + 0, 0, buf, 0x9, SHUT_TIMEOUT); */
|
||||
if (res < 0)
|
||||
{
|
||||
upsdebugx(2, "Unable to get DEVICE descriptor (%s)", shut_strerror());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (res < 9)
|
||||
{
|
||||
upsdebugx(2, "DEVICE descriptor too short (expected %d, got %d)",
|
||||
USB_DT_DEVICE_SIZE, res);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* collect the identifying information of this
|
||||
device. Note that this is safe, because
|
||||
there's no need to claim an interface for
|
||||
this (and therefore we do not yet need to
|
||||
detach any kernel drivers). */
|
||||
|
||||
free(curDevice->Vendor);
|
||||
free(curDevice->Product);
|
||||
free(curDevice->Serial);
|
||||
free(curDevice->Bus);
|
||||
memset(curDevice, '\0', sizeof(*curDevice));
|
||||
|
||||
curDevice->VendorID = dev_descriptor->idVendor;
|
||||
curDevice->ProductID = dev_descriptor->idProduct;
|
||||
curDevice->Bus = strdup("serial");
|
||||
curDevice->Vendor = strdup("Eaton");
|
||||
if (dev_descriptor->iManufacturer) {
|
||||
ret = shut_get_string_simple(*upsfd, dev_descriptor->iManufacturer,
|
||||
string, MAX_STRING_SIZE);
|
||||
if (ret > 0) {
|
||||
curDevice->Vendor = strdup(string);
|
||||
}
|
||||
}
|
||||
|
||||
/* ensure iProduct retrieval */
|
||||
if (dev_descriptor->iProduct) {
|
||||
ret = shut_get_string_simple(*upsfd, dev_descriptor->iProduct, string, MAX_STRING_SIZE);
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
if (ret > 0) {
|
||||
curDevice->Product = strdup(string);
|
||||
} else {
|
||||
curDevice->Product = strdup("unknown");
|
||||
}
|
||||
|
||||
if (dev_descriptor->iSerialNumber) {
|
||||
ret = shut_get_string_simple(*upsfd, dev_descriptor->iSerialNumber, string, 0x25);
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
if (ret > 0) {
|
||||
curDevice->Serial = strdup(string);
|
||||
} else {
|
||||
curDevice->Serial = strdup("unknown");
|
||||
}
|
||||
|
||||
upsdebugx(2, "- VendorID: %04x", curDevice->VendorID);
|
||||
upsdebugx(2, "- ProductID: %04x", curDevice->ProductID);
|
||||
upsdebugx(2, "- Manufacturer: %s", curDevice->Vendor);
|
||||
upsdebugx(2, "- Product: %s", curDevice->Product);
|
||||
upsdebugx(2, "- Serial Number: %s", curDevice->Serial);
|
||||
upsdebugx(2, "- Bus: %s", curDevice->Bus);
|
||||
upsdebugx(2, "Device matches");
|
||||
|
||||
/* Get HID descriptor */
|
||||
desc = (struct my_hid_descriptor *)buf;
|
||||
res = shut_get_descriptor(*upsfd, USB_DT_HID, 0, buf, 0x9);
|
||||
/* res = shut_control_msg(devp, USB_ENDPOINT_IN+1, USB_REQ_GET_DESCRIPTOR,
|
||||
(USB_DT_HID << 8) + 0, 0, buf, 0x9, SHUT_TIMEOUT); */
|
||||
|
||||
if (res < 0)
|
||||
{
|
||||
upsdebugx(2, "Unable to get HID descriptor (%s)", shut_strerror());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (res < 9)
|
||||
{
|
||||
upsdebugx(2, "HID descriptor too short (expected %d, got %d)", 8, res);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* USB_LE16_TO_CPU(desc->wDescriptorLength); */
|
||||
desc->wDescriptorLength = buf[7] | (buf[8] << 8);
|
||||
upsdebugx(2, "HID descriptor retrieved (Reportlen = %u)", desc->wDescriptorLength);
|
||||
|
||||
/* if (!dev->config) {
|
||||
upsdebugx(2, " Couldn't retrieve descriptors");
|
||||
return -1;
|
||||
}*/
|
||||
|
||||
rdlen = desc->wDescriptorLength;
|
||||
|
||||
if (rdlen > (int)sizeof(rdbuf)) {
|
||||
upsdebugx(2, "HID descriptor too long %d (max %d)", rdlen, (int)sizeof(rdbuf));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Get REPORT descriptor */
|
||||
res = shut_get_descriptor(*upsfd, USB_DT_REPORT, 0, rdbuf, rdlen);
|
||||
/* res = shut_control_msg(devp, USB_ENDPOINT_IN+1, USB_REQ_GET_DESCRIPTOR,
|
||||
(USB_DT_REPORT << 8) + 0, 0, ReportDesc,
|
||||
desc->wDescriptorLength, SHUT_TIMEOUT); */
|
||||
if (res == rdlen)
|
||||
{
|
||||
res = callback(*upsfd, curDevice, rdbuf, rdlen);
|
||||
if (res < 1) {
|
||||
upsdebugx(2, "Caller doesn't like this device");
|
||||
return -1;
|
||||
}
|
||||
|
||||
upsdebugx(2, "Report descriptor retrieved (Reportlen = %d)", rdlen);
|
||||
upsdebugx(2, "Found HID device");
|
||||
fflush(stdout);
|
||||
|
||||
return rdlen;
|
||||
}
|
||||
|
||||
if (res < 0)
|
||||
{
|
||||
upsdebugx(2, "Unable to get Report descriptor (%d)", res);
|
||||
}
|
||||
else
|
||||
{
|
||||
upsdebugx(2, "Report descriptor too short (expected %d, got %d)", rdlen, res);
|
||||
}
|
||||
|
||||
upsdebugx(2, "No appropriate HID device found");
|
||||
fflush(stdout);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void libshut_close(int upsfd)
|
||||
{
|
||||
if (upsfd < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
ser_close(upsfd, NULL);
|
||||
}
|
||||
|
||||
/* return the report of ID=type in report
|
||||
* return -1 on failure, report length on success
|
||||
*/
|
||||
int libshut_get_report(int upsfd, int ReportId,
|
||||
unsigned char *raw_buf, int ReportSize )
|
||||
{
|
||||
if (upsfd < 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
upsdebugx(4, "Entering libshut_get_report");
|
||||
|
||||
return shut_control_msg(upsfd,
|
||||
REQUEST_TYPE_GET_REPORT,
|
||||
/* == USB_ENDPOINT_IN + USB_TYPE_CLASS + USB_RECIP_INTERFACE, */
|
||||
0x01,
|
||||
ReportId+(0x03<<8), /* HID_REPORT_TYPE_FEATURE */
|
||||
0, raw_buf, ReportSize, SHUT_TIMEOUT);
|
||||
}
|
||||
|
||||
/* return ReportSize upon success ; -1 otherwise */
|
||||
int libshut_set_report(int upsfd, int ReportId,
|
||||
unsigned char *raw_buf, int ReportSize )
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (upsfd < 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
upsdebugx(1, "Entering libshut_set_report (report %x, len %i)",
|
||||
ReportId, ReportSize);
|
||||
|
||||
upsdebug_hex (4, "==> Report after set", raw_buf, ReportSize);
|
||||
|
||||
ret = shut_control_msg(upsfd,
|
||||
REQUEST_TYPE_SET_REPORT,
|
||||
/* == USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE, */
|
||||
0x09,
|
||||
ReportId+(0x03<<8), /* HID_REPORT_TYPE_FEATURE */
|
||||
0, raw_buf, ReportSize, SHUT_TIMEOUT);
|
||||
|
||||
return ((ret == 0) ? ReportSize : ret);
|
||||
}
|
||||
|
||||
int libshut_get_string(int upsfd, int StringIdx, char *buf, size_t buflen)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (upsfd < 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = shut_get_string_simple(upsfd, StringIdx, buf, buflen);
|
||||
if (ret > 0)
|
||||
upsdebugx(2, "-> String: %s (len = %i/%i)", buf, ret, (int)buflen);
|
||||
else
|
||||
upsdebugx(2, "- Unable to fetch buf");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int libshut_get_interrupt(int upsfd, unsigned char *buf,
|
||||
int bufsize, int timeout)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (upsfd < 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* FIXME: hardcoded interrupt EP => need to get EP descr for IF descr */
|
||||
ret = shut_interrupt_read(upsfd, 0x81, buf, bufsize, timeout);
|
||||
if (ret > 0)
|
||||
upsdebugx(6, " ok");
|
||||
else
|
||||
upsdebugx(6, " none (%i)", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
shut_communication_subdriver_t shut_subdriver = {
|
||||
SHUT_DRIVER_NAME,
|
||||
SHUT_DRIVER_VERSION,
|
||||
libshut_open,
|
||||
libshut_close,
|
||||
libshut_get_report,
|
||||
libshut_set_report,
|
||||
libshut_get_string,
|
||||
libshut_get_interrupt
|
||||
};
|
||||
|
||||
/***********************************************************************/
|
||||
/********** Low level SHUT (Serial HID UPS Transfer) routines **********/
|
||||
/***********************************************************************/
|
||||
|
||||
/*
|
||||
* set RTS to on and DTR to off
|
||||
*
|
||||
* set : 1 to set comm
|
||||
* set : 0 to stop commupsh.
|
||||
*/
|
||||
void setline(int upsfd, int set)
|
||||
{
|
||||
if (upsfd < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
upsdebugx(3, "entering setline(%i)", set);
|
||||
|
||||
if (set == 1) {
|
||||
ser_set_dtr(upsfd, 0);
|
||||
ser_set_rts(upsfd, 1);
|
||||
} else {
|
||||
ser_set_dtr(upsfd, 1);
|
||||
ser_set_rts(upsfd, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* shut_synchronise ()
|
||||
*
|
||||
* initiate communication with the UPS
|
||||
*
|
||||
* return TRUE on success, FALSE on failure
|
||||
*
|
||||
*****************************************************************************/
|
||||
int shut_synchronise(int upsfd)
|
||||
{
|
||||
int retCode = 0;
|
||||
u_char c = SHUT_SYNC, reply;
|
||||
int try;
|
||||
|
||||
upsdebugx (2, "entering shut_synchronise()");
|
||||
reply = '\0';
|
||||
|
||||
/* FIXME: re enable notification support?
|
||||
switch (notification)
|
||||
{
|
||||
case OFF_NOTIFICATION:
|
||||
c = SHUT_SYNC_OFF;
|
||||
break;
|
||||
case LIGHT_NOTIFICATION:
|
||||
c = SHUT_SYNC_LIGHT;
|
||||
break;
|
||||
default:
|
||||
case COMPLETE_NOTIFICATION:
|
||||
c = SHUT_SYNC;
|
||||
break;
|
||||
}
|
||||
*/
|
||||
|
||||
/* Sync with the UPS according to notification */
|
||||
for (try = 0; try < MAX_TRY; try++)
|
||||
{
|
||||
upsdebugx (3, "Syncing communication (try %i)", try);
|
||||
|
||||
if ((ser_send_char(upsfd, c)) == -1)
|
||||
{
|
||||
upsdebugx (3, "Communication error while writing to port");
|
||||
continue;
|
||||
}
|
||||
|
||||
ser_get_char(upsfd, &reply, 1, 0);
|
||||
if (reply == c)
|
||||
{
|
||||
upsdebugx (3, "Syncing and notification setting done");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return retCode;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Compute a SHUT checksum for the packet "buf"
|
||||
*/
|
||||
u_char shut_checksum(const u_char *buf, int bufsize)
|
||||
{
|
||||
int i;
|
||||
u_char chk=0;
|
||||
|
||||
for(i=0; i<bufsize; i++)
|
||||
chk^=buf[i];
|
||||
|
||||
upsdebugx (4, "shut_checksum: %02x", chk);
|
||||
return chk;
|
||||
}
|
||||
|
||||
|
||||
int shut_packet_recv(int upsfd, u_char *Buf, int datalen)
|
||||
{
|
||||
u_char Start[2];
|
||||
u_char Frame[8];
|
||||
u_char Chk[1];
|
||||
u_short Size=8;
|
||||
u_short Pos=0;
|
||||
u_char Retry=0;
|
||||
int recv;
|
||||
shut_data_t sdata;
|
||||
|
||||
upsdebugx (4, "entering shut_packet_recv (%i)", datalen);
|
||||
|
||||
while(datalen>0 && Retry<3)
|
||||
{
|
||||
/* if(serial_read (SHUT_TIMEOUT, &Start[0]) > 0) */
|
||||
if(ser_get_char(upsfd, &Start[0], SHUT_TIMEOUT/1000, 0) > 0)
|
||||
{
|
||||
sdata.shut_pkt.bType = Start[0];
|
||||
if(Start[0]==SHUT_SYNC)
|
||||
{
|
||||
upsdebugx (4, "received SYNC token");
|
||||
memcpy(Buf, Start, 1);
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* if((serial_read (SHUT_TIMEOUT, &Start[1]) > 0) && */
|
||||
if( (ser_get_char(upsfd, &Start[1], SHUT_TIMEOUT/1000, 0) > 0) &&
|
||||
((Start[1]>>4)==(Start[1]&0x0F)))
|
||||
{
|
||||
upsdebug_hex(4, "Receive", Start, 2);
|
||||
Size=Start[1]&0x0F;
|
||||
sdata.shut_pkt.bLength = Size;
|
||||
for(recv=0;recv<Size;recv++)
|
||||
{
|
||||
/* if(serial_read (SHUT_TIMEOUT, &Frame[recv]) < 1) */
|
||||
if(ser_get_char(upsfd, &Frame[recv], SHUT_TIMEOUT/1000, 0) < 1)
|
||||
break;
|
||||
}
|
||||
upsdebug_hex(4, "Receive", Frame, Size);
|
||||
|
||||
/* serial_read (SHUT_TIMEOUT, &Chk[0]); */
|
||||
ser_get_char(upsfd, &Chk[0], SHUT_TIMEOUT/1000, 0);
|
||||
if(Chk[0]==shut_checksum(Frame, Size))
|
||||
{
|
||||
upsdebugx (4, "shut_checksum: %02x => OK", Chk[0]);
|
||||
memcpy(Buf, Frame, Size);
|
||||
datalen-=Size;
|
||||
Buf+=Size;
|
||||
Pos+=Size;
|
||||
Retry=0;
|
||||
|
||||
ser_send_char(upsfd, SHUT_OK);
|
||||
/* shut_token_send(SHUT_OK); */
|
||||
|
||||
if(Start[0]&SHUT_PKT_LAST)
|
||||
{
|
||||
if ((Start[0]&SHUT_PKT_LAST) == SHUT_TYPE_NOTIFY)
|
||||
{
|
||||
/* TODO: process notification (dropped for now) */
|
||||
upsdebugx (4, "=> notification");
|
||||
datalen+=Pos;
|
||||
Pos=0;
|
||||
}
|
||||
else
|
||||
return Pos;
|
||||
}
|
||||
else
|
||||
upsdebugx (4, "need more data (%i)!", datalen);
|
||||
}
|
||||
else
|
||||
{
|
||||
upsdebugx (4, "shut_checksum: %02x => NOK", Chk[0]);
|
||||
ser_send_char(upsfd, SHUT_NOK);
|
||||
/* shut_token_send(SHUT_NOK); */
|
||||
Retry++;
|
||||
}
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
Retry++;
|
||||
} /* while */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**********************************************************************/
|
||||
int shut_interrupt_read(int upsfd, int ep, unsigned char *bytes, int size,
|
||||
int timeout)
|
||||
{
|
||||
/*
|
||||
usleep(timeout * 1000);
|
||||
*/
|
||||
/* FIXME: to be written */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**********************************************************************/
|
||||
int shut_get_string_simple(int upsfd, int index,
|
||||
char *buf, size_t buflen)
|
||||
{
|
||||
unsigned char tbuf[255]; /* Some devices choke on size > 255 */
|
||||
int ret, si, di;
|
||||
|
||||
ret = shut_control_msg(upsfd, USB_ENDPOINT_IN, USB_REQ_GET_DESCRIPTOR,
|
||||
(USB_DT_STRING << 8) + index, 0x0, tbuf, buflen, SHUT_TIMEOUT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (tbuf[1] != USB_DT_STRING)
|
||||
return -EIO;
|
||||
|
||||
if (tbuf[0] > ret)
|
||||
return -EFBIG;
|
||||
|
||||
/* skip the UTF8 zero'ed high bytes */
|
||||
for (di = 0, si = 2; si < tbuf[0]; si += 2)
|
||||
{
|
||||
if (di >= (int)(buflen - 1))
|
||||
break;
|
||||
|
||||
if (tbuf[si + 1]) /* high byte */
|
||||
buf[di++] = '?';
|
||||
else
|
||||
buf[di++] = tbuf[si];
|
||||
}
|
||||
|
||||
buf[di] = 0;
|
||||
return di;
|
||||
}
|
||||
|
||||
/*
|
||||
* Human Interface Device (HID) functions
|
||||
*********************************************************************/
|
||||
|
||||
/**********************************************************************
|
||||
* shut_get_descriptor(int desctype, u_char *pkt)
|
||||
*
|
||||
* get descriptor specified by DescType and return it in Buf
|
||||
*
|
||||
* desctype - from shutdataType
|
||||
* pkt - where to store the report received
|
||||
*
|
||||
* return 0 on success, -1 on failure, -2 on NACK
|
||||
*
|
||||
*********************************************************************/
|
||||
int shut_get_descriptor(int upsfd, unsigned char type,
|
||||
unsigned char index, void *buf, int size)
|
||||
{
|
||||
memset(buf, 0, size);
|
||||
|
||||
upsdebugx (2, "entering shut_get_descriptor(n %02x, %i)", type, size);
|
||||
|
||||
return shut_control_msg(upsfd, USB_ENDPOINT_IN+(type>=USB_DT_HID?1:0),
|
||||
USB_REQ_GET_DESCRIPTOR, (type << 8) + index, 0, buf, size, SHUT_TIMEOUT);
|
||||
}
|
||||
|
||||
/* Take care of a SHUT transfer (sending and receiving data) */
|
||||
int shut_control_msg(int upsfd, int requesttype, int request,
|
||||
int value, int index, unsigned char *bytes, int size, int timeout)
|
||||
{
|
||||
unsigned char shut_pkt[11];
|
||||
short Retry=1, set_pass = -1;
|
||||
short data_size, remaining_size = size;
|
||||
int i;
|
||||
struct shut_ctrltransfer_s ctrl;
|
||||
int ret = 0;
|
||||
|
||||
upsdebugx (3, "entering shut_control_msg");
|
||||
|
||||
/* deal for set requests */
|
||||
if (requesttype == REQUEST_TYPE_SET_REPORT)
|
||||
{
|
||||
set_pass = 1;
|
||||
/* add 8 for the first frame that declares a coming set */
|
||||
remaining_size+= 8;
|
||||
}
|
||||
|
||||
/* build the control request */
|
||||
ctrl.bRequestType = requesttype;
|
||||
ctrl.bRequest = request;
|
||||
ctrl.wValue = value;
|
||||
ctrl.wIndex = index;
|
||||
ctrl.wLength = size;
|
||||
ctrl.data = bytes;
|
||||
ctrl.timeout = timeout;
|
||||
|
||||
align_request(&ctrl);
|
||||
|
||||
/* Send all data */
|
||||
while(remaining_size > 0 && Retry > 0) {
|
||||
|
||||
if (requesttype == REQUEST_TYPE_SET_REPORT) {
|
||||
if (set_pass == 1) {
|
||||
data_size = 8;
|
||||
set_pass++; /* prepare for the next step */
|
||||
}
|
||||
else {
|
||||
data_size = size;
|
||||
upsdebug_hex(4, "data", bytes, data_size);
|
||||
}
|
||||
}
|
||||
else
|
||||
data_size = (size >= 8) ? 8 : remaining_size;
|
||||
|
||||
/* Forge the SHUT Frame */
|
||||
shut_pkt[0] = SHUT_TYPE_REQUEST + ( ((requesttype == REQUEST_TYPE_SET_REPORT) && (remaining_size>8))? 0 : SHUT_PKT_LAST);
|
||||
shut_pkt[1] = (data_size<<4) + data_size;
|
||||
if ( (requesttype == REQUEST_TYPE_SET_REPORT) && (remaining_size < 8) )
|
||||
memcpy(&shut_pkt[2], bytes, data_size); /* we need to send ctrl.data */
|
||||
else
|
||||
memcpy(&shut_pkt[2], &ctrl, 8);
|
||||
shut_pkt[(data_size+3) - 1] = shut_checksum(&shut_pkt[2], data_size);
|
||||
|
||||
/* Packets need only to be sent once
|
||||
* NACK handling should take care of the rest */
|
||||
if (Retry == 1)
|
||||
{
|
||||
ser_send_buf(upsfd, shut_pkt, data_size+3);
|
||||
upsdebug_hex(3, "shut_control_msg", shut_pkt, data_size+3);
|
||||
/* serial_send (shut_pkt, data_size+3); */
|
||||
}
|
||||
|
||||
i = shut_wait_ack (upsfd);
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
if (requesttype == REQUEST_TYPE_SET_REPORT)
|
||||
remaining_size-=data_size;
|
||||
else
|
||||
remaining_size = 0;
|
||||
|
||||
Retry=1;
|
||||
break;
|
||||
case -1:
|
||||
if (Retry >= MAX_TRY)
|
||||
{
|
||||
upsdebugx(2, "Max tries reached while waiting for ACK, still getting errors");
|
||||
|
||||
/* try to resync, and give one more try */
|
||||
Retry--;
|
||||
shut_synchronise(upsfd);
|
||||
return i;
|
||||
}
|
||||
else
|
||||
{
|
||||
upsdebugx(4, "Retry = %i", Retry);
|
||||
/* Send a NACK to get a resend from the UPS */
|
||||
ser_send_char(upsfd, SHUT_NOK);
|
||||
Retry++;
|
||||
}
|
||||
break;
|
||||
case -3:
|
||||
/* FIXME: notification caught => to be processed */
|
||||
|
||||
/* Send a NACK for the moment, to get a resend from the UPS */
|
||||
ser_send_char(upsfd, SHUT_NOK);
|
||||
Retry++;
|
||||
default:
|
||||
;
|
||||
}
|
||||
}
|
||||
if (remaining_size != 0)
|
||||
return -1;
|
||||
|
||||
/* now receive data, except for SET_REPORT */
|
||||
if (requesttype != REQUEST_TYPE_SET_REPORT)
|
||||
ret = shut_packet_recv (upsfd, bytes, size);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**********************************************************************
|
||||
* shut_wait_ack()
|
||||
*
|
||||
* wait for an ACK packet
|
||||
*
|
||||
* returns 0 on success, -1 on error, -2 on NACK, -3 on NOTIFICATION
|
||||
*
|
||||
*********************************************************************/
|
||||
int shut_wait_ack(int upsfd)
|
||||
{
|
||||
int retCode = -1;
|
||||
u_char c = '\0';
|
||||
|
||||
ser_get_char(upsfd, &c, SHUT_TIMEOUT/1000, 0);
|
||||
if (c == SHUT_OK)
|
||||
{
|
||||
upsdebugx (2, "shut_wait_ack(): ACK received");
|
||||
retCode = 0;
|
||||
}
|
||||
else if (c == SHUT_NOK)
|
||||
{
|
||||
upsdebugx (2, "shut_wait_ack(): NACK received");
|
||||
retCode = -2;
|
||||
}
|
||||
else if ((c & SHUT_PKT_LAST) == SHUT_TYPE_NOTIFY)
|
||||
{
|
||||
upsdebugx (2, "shut_wait_ack(): NOTIFY received");
|
||||
retCode = -3;
|
||||
}
|
||||
else if (c == '\0')
|
||||
upsdebugx (2, "shut_wait_ack(): Nothing received");
|
||||
|
||||
return retCode;
|
||||
}
|
||||
87
drivers/libshut.h
Normal file
87
drivers/libshut.h
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/*!
|
||||
* @file libshut.h
|
||||
* @brief HID Library - Generic serial SHUT backend for Generic HID Access (using MGE HIDParser)
|
||||
* SHUT stands for Serial HID UPS Transfer, and was created by MGE UPS SYSTEMS
|
||||
*
|
||||
* @author Copyright (C) 2006 - 2007
|
||||
* Arnaud Quette <aquette.dev@gmail.com>
|
||||
*
|
||||
* This program is sponsored by MGE UPS SYSTEMS - opensource.mgeups.com
|
||||
*
|
||||
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
#ifndef LIBSHUT_H
|
||||
#define LIBSHUT_H
|
||||
|
||||
#include "main.h" /* for subdrv_info_t */
|
||||
#include "nut_stdint.h" /* for uint16_t */
|
||||
|
||||
extern upsdrv_info_t comm_upsdrv_info;
|
||||
|
||||
/*!
|
||||
* SHUTDevice_t: Describe a SHUT device. This structure contains exactly
|
||||
* the 5 pieces of information by which a SHUT device identifies
|
||||
* itself, so it serves as a kind of "fingerprint" of the device. This
|
||||
* information must be matched exactly when reopening a device, and
|
||||
* therefore must not be "improved" or updated by a client
|
||||
* program. Vendor, Product, and Serial can be NULL if the
|
||||
* corresponding string did not exist or could not be retrieved.
|
||||
*/
|
||||
typedef struct SHUTDevice_s {
|
||||
uint16_t VendorID; /*!< Device's Vendor ID */
|
||||
uint16_t ProductID; /*!< Device's Product ID */
|
||||
char* Vendor; /*!< Device's Vendor Name */
|
||||
char* Product; /*!< Device's Product Name */
|
||||
char* Serial; /* Product serial number */
|
||||
char* Bus; /* Bus name, e.g. "003" */
|
||||
} SHUTDevice_t;
|
||||
|
||||
/*!
|
||||
* shut_communication_subdriver_s: structure to describe the communication routines
|
||||
*/
|
||||
typedef struct shut_communication_subdriver_s {
|
||||
char *name; /* name of this subdriver */
|
||||
char *version; /* version of this subdriver */
|
||||
int (*open)(int *upsfd, /* try to open the next available */
|
||||
SHUTDevice_t *curDevice, /* device matching USBDeviceMatcher_t */
|
||||
char *device_path,
|
||||
int (*callback)(int upsfd, SHUTDevice_t *hd, unsigned char *rdbuf, int rdlen));
|
||||
void (*close)(int upsfd);
|
||||
int (*get_report)(int upsfd, int ReportId,
|
||||
unsigned char *raw_buf, int ReportSize );
|
||||
int (*set_report)(int upsfd, int ReportId,
|
||||
unsigned char *raw_buf, int ReportSize );
|
||||
int (*get_string)(int upsfd,
|
||||
int StringIdx, char *buf, size_t buflen);
|
||||
int (*get_interrupt)(int upsfd,
|
||||
unsigned char *buf, int bufsize, int timeout);
|
||||
} shut_communication_subdriver_t;
|
||||
|
||||
extern shut_communication_subdriver_t shut_subdriver;
|
||||
|
||||
/*!
|
||||
* Notification levels
|
||||
* These are however not processed currently
|
||||
*/
|
||||
#define OFF_NOTIFICATION 1 /* notification off */
|
||||
#define LIGHT_NOTIFICATION 2 /* light notification */
|
||||
#define COMPLETE_NOTIFICATION 3 /* complete notification for UPSs which do */
|
||||
/* not support disabling it like some early */
|
||||
/* Ellipse models */
|
||||
#define DEFAULT_NOTIFICATION COMPLETE_NOTIFICATION
|
||||
|
||||
#endif /* LIBSHUT_H */
|
||||
492
drivers/libusb.c
Normal file
492
drivers/libusb.c
Normal file
|
|
@ -0,0 +1,492 @@
|
|||
/*!
|
||||
* @file libusb.c
|
||||
* @brief HID Library - Generic USB communication sub driver (using libusb)
|
||||
*
|
||||
* @author Copyright (C)
|
||||
* 2003 - 2007 Arnaud Quette <aquette.dev@gmail.com>
|
||||
* 2005 - 2007 Peter Selinger <selinger@users.sourceforge.net>
|
||||
*
|
||||
* This program is sponsored by MGE UPS SYSTEMS - opensource.mgeups.com
|
||||
*
|
||||
* The logic of this file is ripped from mge-shut driver (also from
|
||||
* Arnaud Quette), which is a "HID over serial link" UPS driver for
|
||||
* Network UPS Tools <http://www.networkupstools.org/>
|
||||
*
|
||||
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
#include "config.h" /* for HAVE_USB_DETACH_KERNEL_DRIVER_NP flag */
|
||||
#include "common.h" /* for xmalloc, upsdebugx prototypes */
|
||||
#include "usb-common.h"
|
||||
#include "libusb.h"
|
||||
|
||||
/* USB standard state 5000, but we've decreased it to
|
||||
* improve reactivity */
|
||||
#define USB_TIMEOUT 4000
|
||||
|
||||
#define USB_DRIVER_NAME "USB communication driver"
|
||||
#define USB_DRIVER_VERSION "0.31"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t comm_upsdrv_info = {
|
||||
USB_DRIVER_NAME,
|
||||
USB_DRIVER_VERSION,
|
||||
NULL,
|
||||
0,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
#define MAX_REPORT_SIZE 0x1800
|
||||
|
||||
static void libusb_close(usb_dev_handle *udev);
|
||||
|
||||
/* From usbutils: workaround libusb API goofs: "byte" should never be sign extended;
|
||||
* using "char" is trouble. Likewise, sizes should never be negative.
|
||||
*/
|
||||
|
||||
static inline int typesafe_control_msg(usb_dev_handle *dev,
|
||||
unsigned char requesttype, unsigned char request,
|
||||
int value, int index,
|
||||
unsigned char *bytes, unsigned size, int timeout)
|
||||
{
|
||||
return usb_control_msg(dev, requesttype, request, value, index,
|
||||
(char *) bytes, (int) size, timeout);
|
||||
}
|
||||
|
||||
/* invoke matcher against device */
|
||||
static inline int matches(USBDeviceMatcher_t *matcher, USBDevice_t *device) {
|
||||
if (!matcher) {
|
||||
return 1;
|
||||
}
|
||||
return matcher->match_function(device, matcher->privdata);
|
||||
}
|
||||
|
||||
#define usb_control_msg typesafe_control_msg
|
||||
|
||||
/* On success, fill in the curDevice structure and return the report
|
||||
* descriptor length. On failure, return -1.
|
||||
* Note: When callback is not NULL, the report descriptor will be
|
||||
* passed to this function together with the udev and USBDevice_t
|
||||
* information. This callback should return a value > 0 if the device
|
||||
* is accepted, or < 1 if not. If it isn't accepted, the next device
|
||||
* (if any) will be tried, until there are no more devices left.
|
||||
*/
|
||||
static int libusb_open(usb_dev_handle **udevp, USBDevice_t *curDevice, USBDeviceMatcher_t *matcher,
|
||||
int (*callback)(usb_dev_handle *udev, USBDevice_t *hd, unsigned char *rdbuf, int rdlen))
|
||||
{
|
||||
#ifdef HAVE_USB_DETACH_KERNEL_DRIVER_NP
|
||||
int retries;
|
||||
#endif
|
||||
int rdlen1, rdlen2; /* report descriptor length, method 1+2 */
|
||||
USBDeviceMatcher_t *m;
|
||||
struct usb_device *dev;
|
||||
struct usb_bus *bus;
|
||||
usb_dev_handle *udev;
|
||||
struct usb_interface_descriptor *iface;
|
||||
|
||||
int ret, res;
|
||||
unsigned char buf[20];
|
||||
unsigned char *p;
|
||||
char string[256];
|
||||
int i;
|
||||
|
||||
/* report descriptor */
|
||||
unsigned char rdbuf[MAX_REPORT_SIZE];
|
||||
int rdlen;
|
||||
|
||||
/* libusb base init */
|
||||
usb_init();
|
||||
usb_find_busses();
|
||||
usb_find_devices();
|
||||
|
||||
#ifndef __linux__ /* SUN_LIBUSB (confirmed to work on Solaris and FreeBSD) */
|
||||
/* Causes a double free corruption in linux if device is detached! */
|
||||
libusb_close(*udevp);
|
||||
#endif
|
||||
|
||||
for (bus = usb_busses; bus; bus = bus->next) {
|
||||
for (dev = bus->devices; dev; dev = dev->next) {
|
||||
upsdebugx(2, "Checking device (%04X/%04X) (%s/%s)", dev->descriptor.idVendor,
|
||||
dev->descriptor.idProduct, bus->dirname, dev->filename);
|
||||
|
||||
/* supported vendors are now checked by the
|
||||
supplied matcher */
|
||||
|
||||
/* open the device */
|
||||
*udevp = udev = usb_open(dev);
|
||||
if (!udev) {
|
||||
upsdebugx(2, "Failed to open device, skipping. (%s)", usb_strerror());
|
||||
continue;
|
||||
}
|
||||
|
||||
/* collect the identifying information of this
|
||||
device. Note that this is safe, because
|
||||
there's no need to claim an interface for
|
||||
this (and therefore we do not yet need to
|
||||
detach any kernel drivers). */
|
||||
|
||||
free(curDevice->Vendor);
|
||||
free(curDevice->Product);
|
||||
free(curDevice->Serial);
|
||||
free(curDevice->Bus);
|
||||
memset(curDevice, '\0', sizeof(*curDevice));
|
||||
|
||||
curDevice->VendorID = dev->descriptor.idVendor;
|
||||
curDevice->ProductID = dev->descriptor.idProduct;
|
||||
curDevice->Bus = strdup(bus->dirname);
|
||||
|
||||
if (dev->descriptor.iManufacturer) {
|
||||
ret = usb_get_string_simple(udev, dev->descriptor.iManufacturer,
|
||||
string, sizeof(string));
|
||||
if (ret > 0) {
|
||||
curDevice->Vendor = strdup(string);
|
||||
}
|
||||
}
|
||||
|
||||
if (dev->descriptor.iProduct) {
|
||||
ret = usb_get_string_simple(udev, dev->descriptor.iProduct,
|
||||
string, sizeof(string));
|
||||
if (ret > 0) {
|
||||
curDevice->Product = strdup(string);
|
||||
}
|
||||
}
|
||||
|
||||
if (dev->descriptor.iSerialNumber) {
|
||||
ret = usb_get_string_simple(udev, dev->descriptor.iSerialNumber,
|
||||
string, sizeof(string));
|
||||
if (ret > 0) {
|
||||
curDevice->Serial = strdup(string);
|
||||
}
|
||||
}
|
||||
|
||||
upsdebugx(2, "- VendorID: %04x", curDevice->VendorID);
|
||||
upsdebugx(2, "- ProductID: %04x", curDevice->ProductID);
|
||||
upsdebugx(2, "- Manufacturer: %s", curDevice->Vendor ? curDevice->Vendor : "unknown");
|
||||
upsdebugx(2, "- Product: %s", curDevice->Product ? curDevice->Product : "unknown");
|
||||
upsdebugx(2, "- Serial Number: %s", curDevice->Serial ? curDevice->Serial : "unknown");
|
||||
upsdebugx(2, "- Bus: %s", curDevice->Bus ? curDevice->Bus : "unknown");
|
||||
|
||||
upsdebugx(2, "Trying to match device");
|
||||
for (m = matcher; m; m=m->next) {
|
||||
ret = matches(m, curDevice);
|
||||
if (ret==0) {
|
||||
upsdebugx(2, "Device does not match - skipping");
|
||||
goto next_device;
|
||||
} else if (ret==-1) {
|
||||
fatal_with_errno(EXIT_FAILURE, "matcher");
|
||||
goto next_device;
|
||||
} else if (ret==-2) {
|
||||
upsdebugx(2, "matcher: unspecified error");
|
||||
goto next_device;
|
||||
}
|
||||
}
|
||||
upsdebugx(2, "Device matches");
|
||||
|
||||
/* Now we have matched the device we wanted. Claim it. */
|
||||
|
||||
#ifdef HAVE_USB_DETACH_KERNEL_DRIVER_NP
|
||||
/* this method requires at least libusb 0.1.8:
|
||||
* it force device claiming by unbinding
|
||||
* attached driver... From libhid */
|
||||
retries = 3;
|
||||
while (usb_claim_interface(udev, 0) < 0) {
|
||||
|
||||
upsdebugx(2, "failed to claim USB device: %s", usb_strerror());
|
||||
|
||||
if (usb_detach_kernel_driver_np(udev, 0) < 0) {
|
||||
upsdebugx(2, "failed to detach kernel driver from USB device: %s", usb_strerror());
|
||||
} else {
|
||||
upsdebugx(2, "detached kernel driver from USB device...");
|
||||
}
|
||||
|
||||
if (retries-- > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
fatalx(EXIT_FAILURE, "Can't claim USB device [%04x:%04x]: %s", curDevice->VendorID, curDevice->ProductID, usb_strerror());
|
||||
}
|
||||
#else
|
||||
if (usb_claim_interface(udev, 0) < 0) {
|
||||
fatalx(EXIT_FAILURE, "Can't claim USB device [%04x:%04x]: %s", curDevice->VendorID, curDevice->ProductID, usb_strerror());
|
||||
}
|
||||
#endif
|
||||
|
||||
/* set default interface */
|
||||
usb_set_altinterface(udev, 0);
|
||||
|
||||
if (!callback) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!dev->config) { /* ?? this should never happen */
|
||||
upsdebugx(2, " Couldn't retrieve descriptors");
|
||||
goto next_device;
|
||||
}
|
||||
|
||||
rdlen1 = -1;
|
||||
rdlen2 = -1;
|
||||
|
||||
/* Get HID descriptor */
|
||||
|
||||
/* FIRST METHOD: ask for HID descriptor directly. */
|
||||
/* res = usb_get_descriptor(udev, USB_DT_HID, 0, buf, 0x9); */
|
||||
res = usb_control_msg(udev, USB_ENDPOINT_IN+1, USB_REQ_GET_DESCRIPTOR,
|
||||
(USB_DT_HID << 8) + 0, 0, buf, 0x9, USB_TIMEOUT);
|
||||
|
||||
if (res < 0) {
|
||||
upsdebugx(2, "Unable to get HID descriptor (%s)", usb_strerror());
|
||||
} else if (res < 9) {
|
||||
upsdebugx(2, "HID descriptor too short (expected %d, got %d)", 8, res);
|
||||
} else {
|
||||
|
||||
upsdebug_hex(3, "HID descriptor, method 1", buf, 9);
|
||||
|
||||
rdlen1 = buf[7] | (buf[8] << 8);
|
||||
}
|
||||
|
||||
if (rdlen1 < -1) {
|
||||
upsdebugx(2, "Warning: HID descriptor, method 1 failed");
|
||||
}
|
||||
|
||||
/* SECOND METHOD: find HID descriptor among "extra" bytes of
|
||||
interface descriptor, i.e., bytes tucked onto the end of
|
||||
descriptor 2. */
|
||||
|
||||
/* Note: on some broken UPS's (e.g. Tripp Lite Smart1000LCD),
|
||||
only this second method gives the correct result */
|
||||
|
||||
/* for now, we always assume configuration 0, interface 0,
|
||||
altsetting 0, as above. */
|
||||
iface = &dev->config[0].interface[0].altsetting[0];
|
||||
for (i=0; i<iface->extralen; i+=iface->extra[i]) {
|
||||
upsdebugx(4, "i=%d, extra[i]=%02x, extra[i+1]=%02x", i,
|
||||
iface->extra[i], iface->extra[i+1]);
|
||||
if (i+9 <= iface->extralen && iface->extra[i] >= 9 && iface->extra[i+1] == 0x21) {
|
||||
p = &iface->extra[i];
|
||||
upsdebug_hex(3, "HID descriptor, method 2", p, 9);
|
||||
rdlen2 = p[7] | (p[8] << 8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (rdlen2 < -1) {
|
||||
upsdebugx(2, "Warning: HID descriptor, method 2 failed");
|
||||
}
|
||||
|
||||
/* when available, always choose the second value, as it
|
||||
seems to be more reliable (it is the one reported e.g. by
|
||||
lsusb). Note: if the need arises, can change this to use
|
||||
the maximum of the two values instead. */
|
||||
rdlen = rdlen2 >= 0 ? rdlen2 : rdlen1;
|
||||
|
||||
if (rdlen < 0) {
|
||||
upsdebugx(2, "Unable to retrieve any HID descriptor");
|
||||
goto next_device;
|
||||
}
|
||||
if (rdlen1 >= 0 && rdlen2 >= 0 && rdlen1 != rdlen2) {
|
||||
upsdebugx(2, "Warning: two different HID descriptors retrieved (Reportlen = %d vs. %d)", rdlen1, rdlen2);
|
||||
}
|
||||
|
||||
upsdebugx(2, "HID descriptor length %d", rdlen);
|
||||
|
||||
if (rdlen > (int)sizeof(rdbuf)) {
|
||||
upsdebugx(2, "HID descriptor too long %d (max %d)", rdlen, (int)sizeof(rdbuf));
|
||||
goto next_device;
|
||||
}
|
||||
|
||||
/* res = usb_get_descriptor(udev, USB_DT_REPORT, 0, bigbuf, rdlen); */
|
||||
res = usb_control_msg(udev, USB_ENDPOINT_IN+1, USB_REQ_GET_DESCRIPTOR,
|
||||
(USB_DT_REPORT << 8) + 0, 0, rdbuf, rdlen, USB_TIMEOUT);
|
||||
|
||||
if (res < 0)
|
||||
{
|
||||
upsdebug_with_errno(2, "Unable to get Report descriptor");
|
||||
goto next_device;
|
||||
}
|
||||
|
||||
if (res < rdlen)
|
||||
{
|
||||
upsdebugx(2, "Warning: report descriptor too short (expected %d, got %d)", rdlen, res);
|
||||
rdlen = res; /* correct rdlen if necessary */
|
||||
}
|
||||
|
||||
res = callback(udev, curDevice, rdbuf, rdlen);
|
||||
if (res < 1) {
|
||||
upsdebugx(2, "Caller doesn't like this device");
|
||||
goto next_device;
|
||||
}
|
||||
|
||||
upsdebugx(2, "Report descriptor retrieved (Reportlen = %d)", rdlen);
|
||||
upsdebugx(2, "Found HID device");
|
||||
fflush(stdout);
|
||||
|
||||
return rdlen;
|
||||
|
||||
next_device:
|
||||
usb_close(udev);
|
||||
}
|
||||
}
|
||||
|
||||
*udevp = NULL;
|
||||
upsdebugx(2, "No appropriate HID device found");
|
||||
fflush(stdout);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Error handler for usb_get/set_* functions. Return value > 0 success,
|
||||
* 0 unknown or temporary failure (ignored), < 0 permanent failure (reconnect)
|
||||
*/
|
||||
static int libusb_strerror(const int ret, const char *desc)
|
||||
{
|
||||
if (ret > 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
switch(ret)
|
||||
{
|
||||
case -EBUSY: /* Device or resource busy */
|
||||
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 */
|
||||
case -EPIPE: /* Broken pipe */
|
||||
case -ENOSYS: /* Function not implemented */
|
||||
upslogx(LOG_DEBUG, "%s: %s", desc, usb_strerror());
|
||||
return ret;
|
||||
|
||||
case -ETIMEDOUT: /* Connection timed out */
|
||||
upsdebugx(2, "%s: Connection timed out", desc);
|
||||
return 0;
|
||||
|
||||
case -EOVERFLOW: /* Value too large for defined data type */
|
||||
case -EPROTO: /* Protocol error */
|
||||
upsdebugx(2, "%s: %s", desc, usb_strerror());
|
||||
return 0;
|
||||
|
||||
default: /* Undetermined, log only */
|
||||
upslogx(LOG_DEBUG, "%s: %s", desc, usb_strerror());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* return the report of ID=type in report
|
||||
* return -1 on failure, report length on success
|
||||
*/
|
||||
|
||||
static int libusb_get_report(usb_dev_handle *udev, int ReportId, unsigned char *raw_buf, int ReportSize )
|
||||
{
|
||||
int ret;
|
||||
|
||||
upsdebugx(4, "Entering libusb_get_report");
|
||||
|
||||
if (!udev) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = usb_control_msg(udev,
|
||||
USB_ENDPOINT_IN + USB_TYPE_CLASS + USB_RECIP_INTERFACE,
|
||||
0x01, /* HID_REPORT_GET */
|
||||
ReportId+(0x03<<8), /* HID_REPORT_TYPE_FEATURE */
|
||||
0, raw_buf, ReportSize, USB_TIMEOUT);
|
||||
|
||||
/* Ignore "protocol stall" (for unsupported request) on control endpoint */
|
||||
if (ret == -EPIPE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return libusb_strerror(ret, __func__);
|
||||
}
|
||||
|
||||
static int libusb_set_report(usb_dev_handle *udev, int ReportId, unsigned char *raw_buf, int ReportSize )
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!udev) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = usb_control_msg(udev,
|
||||
USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE,
|
||||
0x09, /* HID_REPORT_SET = 0x09*/
|
||||
ReportId+(0x03<<8), /* HID_REPORT_TYPE_FEATURE */
|
||||
0, raw_buf, ReportSize, USB_TIMEOUT);
|
||||
|
||||
/* Ignore "protocol stall" (for unsupported request) on control endpoint */
|
||||
if (ret == -EPIPE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return libusb_strerror(ret, __func__);
|
||||
}
|
||||
|
||||
static int libusb_get_string(usb_dev_handle *udev, int StringIdx, char *buf, size_t buflen)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!udev) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = usb_get_string_simple(udev, StringIdx, buf, buflen);
|
||||
|
||||
return libusb_strerror(ret, __func__);
|
||||
}
|
||||
|
||||
static int libusb_get_interrupt(usb_dev_handle *udev, unsigned char *buf, int bufsize, int timeout)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!udev) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* FIXME: hardcoded interrupt EP => need to get EP descr for IF descr */
|
||||
ret = usb_interrupt_read(udev, 0x81, (char *)buf, bufsize, timeout);
|
||||
|
||||
/* Clear stall condition */
|
||||
if (ret == -EPIPE) {
|
||||
ret = usb_clear_halt(udev, 0x81);
|
||||
}
|
||||
|
||||
return libusb_strerror(ret, __func__);
|
||||
}
|
||||
|
||||
static void libusb_close(usb_dev_handle *udev)
|
||||
{
|
||||
if (!udev) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* usb_release_interface() sometimes blocks and goes
|
||||
into uninterruptible sleep. So don't do it. */
|
||||
/* usb_release_interface(udev, 0); */
|
||||
usb_close(udev);
|
||||
}
|
||||
|
||||
usb_communication_subdriver_t usb_subdriver = {
|
||||
USB_DRIVER_VERSION,
|
||||
USB_DRIVER_NAME,
|
||||
libusb_open,
|
||||
libusb_close,
|
||||
libusb_get_report,
|
||||
libusb_set_report,
|
||||
libusb_get_string,
|
||||
libusb_get_interrupt
|
||||
};
|
||||
66
drivers/libusb.h
Normal file
66
drivers/libusb.h
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/*!
|
||||
* @file libusb.h
|
||||
* @brief HID Library - Generic USB backend for Generic HID Access (using MGE HIDParser)
|
||||
*
|
||||
* @author Copyright (C)
|
||||
* 2003 - 2006 Arnaud Quette <aquette.dev@gmail.com>
|
||||
* 2005 Peter Selinger <selinger@users.sourceforge.net>
|
||||
*
|
||||
* This program is sponsored by MGE UPS SYSTEMS - opensource.mgeups.com
|
||||
*
|
||||
* The logic of this file is ripped from mge-shut driver (also from
|
||||
* Arnaud Quette), which is a "HID over serial link" UPS driver for
|
||||
* Network UPS Tools <http://www.networkupstools.org/>
|
||||
*
|
||||
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
#ifndef LIBUSB_H
|
||||
#define LIBUSB_H
|
||||
|
||||
#include "main.h" /* for subdrv_info_t */
|
||||
#include "usb-common.h" /* for USBDevice_t and USBDeviceMatcher_t */
|
||||
|
||||
#include <usb.h> /* libusb header file */
|
||||
|
||||
extern upsdrv_info_t comm_upsdrv_info;
|
||||
|
||||
/*!
|
||||
* usb_communication_subdriver_s: structure to describe the communication routines
|
||||
* @name: can be either "shut" for Serial HID UPS Transfer (from MGE) or "usb"
|
||||
*/
|
||||
typedef struct usb_communication_subdriver_s {
|
||||
char *name; /* name of this subdriver */
|
||||
char *version; /* version of this subdriver */
|
||||
int (*open)(usb_dev_handle **sdevp, /* try to open the next available */
|
||||
USBDevice_t *curDevice, /* device matching USBDeviceMatcher_t */
|
||||
USBDeviceMatcher_t *matcher,
|
||||
int (*callback)(usb_dev_handle *udev, USBDevice_t *hd, unsigned char *rdbuf, int rdlen));
|
||||
void (*close)(usb_dev_handle *sdev);
|
||||
int (*get_report)(usb_dev_handle *sdev, int ReportId,
|
||||
unsigned char *raw_buf, int ReportSize );
|
||||
int (*set_report)(usb_dev_handle *sdev, int ReportId,
|
||||
unsigned char *raw_buf, int ReportSize );
|
||||
int (*get_string)(usb_dev_handle *sdev,
|
||||
int StringIdx, char *buf, size_t buflen);
|
||||
int (*get_interrupt)(usb_dev_handle *sdev,
|
||||
unsigned char *buf, int bufsize, int timeout);
|
||||
} usb_communication_subdriver_t;
|
||||
|
||||
extern usb_communication_subdriver_t usb_subdriver;
|
||||
|
||||
#endif /* LIBUSB_H */
|
||||
|
||||
141
drivers/liebert-hid.c
Normal file
141
drivers/liebert-hid.c
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
/* liebert-hid.c - subdriver to monitor Liebert USB/HID devices with NUT
|
||||
*
|
||||
* Copyright (C)
|
||||
* 2003 - 2008 Arnaud Quette <arnaud.quette@free.fr>
|
||||
* 2005 - 2006 Peter Selinger <selinger@users.sourceforge.net>
|
||||
* 2007 Charles Lepple <clepple@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "main.h" /* for getval() */
|
||||
#include "usbhid-ups.h"
|
||||
#include "liebert-hid.h"
|
||||
#include "usb-common.h"
|
||||
|
||||
#define LIEBERT_HID_VERSION "Liebert HID 0.3"
|
||||
/* FIXME: experimental flag to be put in upsdrv_info */
|
||||
|
||||
/* Liebert */
|
||||
#define LIEBERT_VENDORID 0x06da
|
||||
|
||||
/* USB IDs device table */
|
||||
static usb_device_id_t liebert_usb_device_table[] = {
|
||||
/* various models */
|
||||
{ USB_DEVICE(LIEBERT_VENDORID, 0xffff), NULL },
|
||||
|
||||
/* Terminating entry */
|
||||
{ -1, -1, NULL }
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* Vendor-specific usage table */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
/* LIEBERT usage table */
|
||||
static usage_lkp_t liebert_usage_lkp[] = {
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
static usage_tables_t liebert_utab[] = {
|
||||
liebert_usage_lkp,
|
||||
hid_usage_lkp,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* HID2NUT lookup table */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
static hid_info_t liebert_hid2nut[] = {
|
||||
|
||||
#if 0
|
||||
{ "unmapped.ups.powersummary.flowid", 0, 0, "UPS.PowerSummary.FlowID", NULL, "%.0f", 0, NULL },
|
||||
{ "unmapped.ups.powersummary.powersummaryid", 0, 0, "UPS.PowerSummary.PowerSummaryID", NULL, "%.0f", 0, NULL },
|
||||
{ "unmapped.ups.powersummary.designcapacity", 0, 0, "UPS.PowerSummary.DesignCapacity", NULL, "%.0f", 0, NULL },
|
||||
{ "unmapped.ups.powersummary.capacitygranularity1", 0, 0, "UPS.PowerSummary.CapacityGranularity1", NULL, "%.0f", 0, NULL },
|
||||
{ "unmapped.ups.powersummary.capacitymode", 0, 0, "UPS.PowerSummary.CapacityMode", NULL, "%.0f", 0, NULL },
|
||||
{ "unmapped.ups.powersummary.rechargeable", 0, 0, "UPS.PowerSummary.Rechargeable", NULL, "%.0f", 0, NULL },
|
||||
{ "unmapped.ups.powersummary.iproduct", 0, 0, "UPS.PowerSummary.iProduct", NULL, "%.0f", 0, NULL },
|
||||
{ "unmapped.ups.powersummary.imanufacturer", 0, 0, "UPS.PowerSummary.iManufacturer", NULL, "%.0f", 0, NULL },
|
||||
#endif
|
||||
|
||||
{ "battery.voltage", 0, 0, "UPS.PowerSummary.Voltage", NULL, "%.0f", 0, NULL },
|
||||
{ "battery.voltage.nominal", 0, 0, "UPS.PowerSummary.ConfigVoltage", NULL, "%.0f", 0, NULL },
|
||||
{ "battery.charge", 0, 0, "UPS.PowerSummary.RemainingCapacity", NULL, "%.0f", 0, NULL },
|
||||
{ "battery.runtime", 0, 0, "UPS.PowerSummary.RunTimeToEmpty", NULL, "%.0f", 0, NULL },
|
||||
{ "battery.type", 0, 0, "UPS.PowerSummary.iDeviceChemistry", NULL, "%s", 0, stringid_conversion },
|
||||
|
||||
{ "ups.load", 0, 0, "UPS.PowerSummary.PercentLoad", NULL, "%.0f", 0, NULL },
|
||||
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.ACPresent", NULL, "%.0f", HU_FLAG_QUICK_POLL, online_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.BelowRemainingCapacityLimit", NULL, "%.0f", HU_FLAG_QUICK_POLL, lowbatt_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.Charging", NULL, "%.0f", HU_FLAG_QUICK_POLL, charging_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.Discharging", NULL, "%.0f", HU_FLAG_QUICK_POLL, discharging_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.Overload", NULL, "%.0f", 0, overload_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.ShutdownImminent", NULL, "%.0f", 0, shutdownimm_info },
|
||||
|
||||
/* end of structure. */
|
||||
{ NULL, 0, 0, NULL, NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
static char *liebert_format_model(HIDDevice_t *hd) {
|
||||
return hd->Product;
|
||||
}
|
||||
|
||||
static char *liebert_format_mfr(HIDDevice_t *hd) {
|
||||
return hd->Vendor ? hd->Vendor : "Liebert";
|
||||
}
|
||||
|
||||
static char *liebert_format_serial(HIDDevice_t *hd) {
|
||||
return hd->Serial;
|
||||
}
|
||||
|
||||
/* this function allows the subdriver to "claim" a device: return 1 if
|
||||
* the device is supported by this subdriver, else 0. */
|
||||
static int liebert_claim(HIDDevice_t *hd) {
|
||||
|
||||
int status = is_usb_device_supported(liebert_usb_device_table, hd->VendorID,
|
||||
hd->ProductID);
|
||||
|
||||
switch (status) {
|
||||
|
||||
case POSSIBLY_SUPPORTED:
|
||||
/* by default, reject, unless the productid option is given */
|
||||
if (getval("productid")) {
|
||||
return 1;
|
||||
}
|
||||
possibly_supported("Liebert", hd);
|
||||
return 0;
|
||||
|
||||
case SUPPORTED:
|
||||
return 1;
|
||||
|
||||
case NOT_SUPPORTED:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
subdriver_t liebert_subdriver = {
|
||||
LIEBERT_HID_VERSION,
|
||||
liebert_claim,
|
||||
liebert_utab,
|
||||
liebert_hid2nut,
|
||||
liebert_format_model,
|
||||
liebert_format_mfr,
|
||||
liebert_format_serial,
|
||||
};
|
||||
30
drivers/liebert-hid.h
Normal file
30
drivers/liebert-hid.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/* liebert-hid.h - subdriver to monitor Liebert USB/HID devices with NUT
|
||||
*
|
||||
* Copyright (C)
|
||||
* 2003 - 2005 Arnaud Quette <arnaud.quette@free.fr>
|
||||
* 2005 - 2006 Peter Selinger <selinger@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LIEBERT_HID_H
|
||||
#define LIEBERT_HID_H
|
||||
|
||||
#include "usbhid-ups.h"
|
||||
|
||||
extern subdriver_t liebert_subdriver;
|
||||
|
||||
#endif /* LIEBERT_HID_H */
|
||||
191
drivers/liebert.c
Normal file
191
drivers/liebert.c
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
/* liebert.c - support for Liebert UPS models via MultiLink cable.
|
||||
|
||||
Copyright (C) 2002 Russell Kroll <rkroll@exploits.org>
|
||||
|
||||
Based on old-style multilink.c driver:
|
||||
|
||||
Copyright (C) 2001 Rick Lyons <rick@powerup.com.au>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "serial.h"
|
||||
|
||||
#define DRIVER_NAME "Liebert MultiLink UPS driver"
|
||||
#define DRIVER_VERSION "1.02"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Russell Kroll <rkroll@exploits.org>\n" \
|
||||
"Rick Lyons <rick@powerup.com.au>",
|
||||
DRV_EXPERIMENTAL,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
#define ML_ONBATTERY 0x55
|
||||
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
/* XXX: replace with a proper shutdown function (raise DTR) */
|
||||
|
||||
/* worse yet: stock cables don't support shutdown at all */
|
||||
|
||||
fatalx(EXIT_FAILURE, "shutdown not supported");
|
||||
}
|
||||
|
||||
void upsdrv_initinfo(void)
|
||||
{
|
||||
char *tmp;
|
||||
|
||||
tmp = getval("mfr");
|
||||
|
||||
if (!tmp)
|
||||
dstate_setinfo("ups.mfr", "Liebert");
|
||||
else
|
||||
dstate_setinfo("ups.mfr", "%s", tmp);
|
||||
|
||||
tmp = getval("model");
|
||||
|
||||
if (!tmp)
|
||||
dstate_setinfo("ups.model", "MultiLink");
|
||||
else
|
||||
dstate_setinfo("ups.model", "%s", tmp);
|
||||
}
|
||||
|
||||
/* require this many OBs or LBs before actually setting it */
|
||||
#define DEBOUNCE 3
|
||||
|
||||
/* normal idle loop - keep up with the current state of the UPS */
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
unsigned char c;
|
||||
unsigned int ob, lb;
|
||||
static unsigned int ob_state = 0, ob_last = 0, ob_ctr = 0;
|
||||
static unsigned int lb_state = 0, lb_last = 0, lb_ctr = 0;
|
||||
|
||||
ob = lb = 0;
|
||||
|
||||
/* the UPS connects RX to TX when on battery, so test for loopback */
|
||||
|
||||
ser_flush_in(upsfd, "", 0);
|
||||
|
||||
c = ML_ONBATTERY;
|
||||
ser_send_char(upsfd, c);
|
||||
if (ser_get_char(upsfd, &c, 1, 0) == 1) {
|
||||
while (ser_get_char(upsfd, &c, 1, 0) == 1)
|
||||
continue;
|
||||
if (c == ML_ONBATTERY)
|
||||
ob = 1;
|
||||
}
|
||||
|
||||
if (ser_get_dcd(upsfd))
|
||||
lb = 1;
|
||||
|
||||
/* state machine below to ensure status changes are debounced */
|
||||
|
||||
/* OB/OL state change: reset counter */
|
||||
if (ob_last != ob)
|
||||
ob_ctr = 0;
|
||||
else
|
||||
ob_ctr++;
|
||||
|
||||
upsdebugx(2, "OB: state %d last %d now %d ctr %d",
|
||||
ob_state, ob_last, ob, ob_ctr);
|
||||
|
||||
if (ob_ctr >= DEBOUNCE) {
|
||||
|
||||
if (ob != ob_state) {
|
||||
|
||||
upsdebugx(2, "OB: toggling state");
|
||||
|
||||
if (ob_state == 0)
|
||||
ob_state = 1;
|
||||
else
|
||||
ob_state = 0;
|
||||
}
|
||||
}
|
||||
|
||||
ob_last = ob;
|
||||
|
||||
/* now do it again for LB */
|
||||
|
||||
/* state change: reset counter */
|
||||
if (lb_last != lb)
|
||||
lb_ctr = 0;
|
||||
else
|
||||
lb_ctr++;
|
||||
|
||||
upsdebugx(2, "LB: state %d last %d now %d ctr %d",
|
||||
lb_state, lb_last, lb, lb_ctr);
|
||||
|
||||
if (lb_ctr >= DEBOUNCE) {
|
||||
|
||||
if (lb != lb_state) {
|
||||
|
||||
upsdebugx(2, "LB: toggling state");
|
||||
|
||||
if (lb_state == 0)
|
||||
lb_state = 1;
|
||||
else
|
||||
lb_state = 0;
|
||||
}
|
||||
}
|
||||
|
||||
lb_last = lb;
|
||||
|
||||
status_init();
|
||||
|
||||
if (ob_state == 1)
|
||||
status_set("OB"); /* on battery */
|
||||
else
|
||||
status_set("OL"); /* on line */
|
||||
|
||||
if (lb_state == 1)
|
||||
status_set("LB"); /* low battery */
|
||||
|
||||
status_commit();
|
||||
dstate_dataok();
|
||||
}
|
||||
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
addvar(VAR_VALUE, "mfr", "Override manufacturer name");
|
||||
addvar(VAR_VALUE, "model", "Override model name");
|
||||
}
|
||||
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
}
|
||||
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
upsfd = ser_open(device_path);
|
||||
|
||||
/* Speed should not matter (see comments in upsdrv_updateinfo),
|
||||
* but set it relatively low in case there are problems with higher
|
||||
* speeds. */
|
||||
ser_set_speed(upsfd, device_path, B9600);
|
||||
|
||||
/* raise RTS */
|
||||
ser_set_rts(upsfd, 1);
|
||||
}
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
ser_close(upsfd, device_path);
|
||||
}
|
||||
292
drivers/liebertgxt2.c
Normal file
292
drivers/liebertgxt2.c
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
/* liebertgxt2.c - driver for Liebert GXT2, using the ESP2 protocol
|
||||
*
|
||||
* Copyright (C)
|
||||
* 2009 Richard Gregory <r.gregory liverpool ac uk>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "serial.h"
|
||||
#include "timehead.h"
|
||||
|
||||
#define DRIVER_NAME "Liebert GXT2 serial UPS driver"
|
||||
#define DRIVER_VERSION "0.01"
|
||||
|
||||
static int instcmd(const char *cmdname, const char *extra);
|
||||
static int setvar(const char *varname, const char *val);
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Richard Gregory <r.gregory liv ac uk>",
|
||||
DRV_EXPERIMENTAL,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const unsigned char
|
||||
/* Bit field information provided by Spiros Ioannou */
|
||||
/* Ordered on MSB to LSB. Shown as DESCRIPTION (bit number), starting at 0. */
|
||||
cmd_bitfield1[] = { 1,148,2,1,1,153 }, /* INPUT_OVERVOLTAGE, BATTERY_TEST_STATE, OVERTEMP_WARNING, INRUSH_LIMIT_ON, UTILITY_STATE, ON_INVERTER, DC_DC_CONVERTER_STATE, PFC_ON */
|
||||
cmd_bitfield2[] = { 1,148,2,1,2,154 }, /* BUCK_ON (9), DIAG_LINK_SET(7), BOOST_ON(6), REPLACE_BATTERY(5), BATTERY_LIFE_ENHANCER_ON(4), BATTERY_CHARGED (1), ON_BYPASS (0) */
|
||||
cmd_bitfield3[] = { 1,148,2,1,3,155 }, /* CHECK_AIR_FILTER (10), BAD_BYPASS_PWR (8), OUTPUT_OVERVOLTAGE (7), OUTPUT_UNDERVOLTAGE (6), LOW_BATTERY (5), CHARGER_FAIL (3), SHUTDOWN_PENDING (2), BAD_INPUT_FREQ (1), UPS_OVERLOAD (0) */
|
||||
cmd_bitfield7[] = { 1,148,2,1,7,159 }, /* AMBIENT_OVERTEMP (2) */
|
||||
cmd_battestres[] = { 1,148,2,1,12,164 }, /* BATTERY_TEST_RESULT */
|
||||
cmd_selftestres[] = { 1,148,2,1,13,165 }; /* SELF_TEST_RESULT */
|
||||
|
||||
static char cksum(const char *buf, const size_t len)
|
||||
{
|
||||
char sum = 0;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
sum += buf[i];
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
static int do_command(const unsigned char *command, char *reply)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = ser_send_buf(upsfd, command, 6);
|
||||
if (ret < 0) {
|
||||
upsdebug_with_errno(2, "send");
|
||||
return -1;
|
||||
} else if (ret < 6) {
|
||||
upsdebug_hex(2, "send: truncated", command, ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
upsdebug_hex(2, "send", command, ret);
|
||||
|
||||
ret = ser_get_buf(upsfd, reply, 8, 1, 0);
|
||||
if (ret < 0) {
|
||||
upsdebug_with_errno(2, "read");
|
||||
return -1;
|
||||
} else if (ret < 6) {
|
||||
upsdebug_hex(2, "read: truncated", reply, ret);
|
||||
return -1;
|
||||
} else if (reply[7] != cksum(reply, 7)) {
|
||||
upsdebug_hex(2, "read: checksum error", reply, ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
upsdebug_hex(2, "read", reply, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void upsdrv_initinfo(void)
|
||||
{
|
||||
struct {
|
||||
const char *var;
|
||||
} vartab[] = {
|
||||
{ "ups.model" },
|
||||
{ "ups.serial" },
|
||||
{ "ups.mfr.date" },
|
||||
{ "ups.firmware" },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
char buf[LARGEBUF], *s;
|
||||
int i;
|
||||
|
||||
dstate_setinfo("ups.mfr", "%s", "Liebert");
|
||||
|
||||
for (i = 0; i < 37; i++) {
|
||||
char command[6], reply[8];
|
||||
int ret;
|
||||
|
||||
snprintf(command, sizeof(command), "\x01\x88\x02\x01%c", i+4);
|
||||
command[5] = cksum(command, 5);
|
||||
|
||||
ret = do_command((unsigned char *)command, reply);
|
||||
if (ret < 8) {
|
||||
fatalx(EXIT_FAILURE, "GTX2 capable UPS not detected");
|
||||
}
|
||||
|
||||
buf[i<<1] = reply[6];
|
||||
buf[(i<<1)+1] = reply[5];
|
||||
}
|
||||
|
||||
buf[i<<1] = 0;
|
||||
|
||||
for (s = strtok(buf, " "), i = 0; s && vartab[i].var; s = strtok(NULL, " "), i++) {
|
||||
dstate_setinfo(vartab[i].var, "%s", s);
|
||||
}
|
||||
|
||||
upsh.instcmd = instcmd;
|
||||
upsh.setvar = setvar;
|
||||
}
|
||||
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
struct {
|
||||
const unsigned char cmd[6];
|
||||
const char *var;
|
||||
const char *fmt;
|
||||
const double mult;
|
||||
} vartab[] = {
|
||||
{ { 1,149,2,1,4,157 }, "battery.charge", "%.0f", 1.0 },
|
||||
{ { 1,149,2,1,1,154 }, "battery.runtime", "%.0f", 60 },
|
||||
{ { 1,149,2,1,2,155 }, "battery.voltage", "%.1f", 0.1 },
|
||||
{ { 1,161,2,1,13,178 }, "battery.voltage.nominal", "%.1f", 0.1 },
|
||||
{ { 1,149,2,1,7,160 }, "ups.load", "%.0f", 1.0 },
|
||||
{ { 1,149,2,1,6,159 }, "ups.power", "%.0f", 1.0 },
|
||||
{ { 1,161,2,1,8,173 }, "ups.power.nominal", "%.0f", 1.0 },
|
||||
{ { 1,149,2,1,5,158 }, "ups.realpower", "%.0f", 1.0 },
|
||||
{ { 1,149,2,1,14,167 }, "ups.temperature", "%.1f", 0.1 },
|
||||
{ { 1,144,2,1,1,149 }, "input.voltage", "%.1f", 0.1 },
|
||||
{ { 1,149,2,1,8,161 }, "input.frequency", "%.1f", 0.1 },
|
||||
{ { 1,149,2,1,10,163 }, "input.frequency.nominal", "%.1f", 0.1 },
|
||||
{ { 1,144,2,1,5,153 }, "input.bypass.voltage", "%.1f", 0.1 },
|
||||
{ { 1,144,2,1,3,151 }, "output.voltage", "%.1f", 0.1 },
|
||||
{ { 1,149,2,1,9,162 }, "output.frequency", "%.1f", 0.1 },
|
||||
{ { 1,144,2,1,4,152 }, "output.current", "%.1f", 0.1 },
|
||||
{ { 0 }, NULL, NULL, 0 }
|
||||
};
|
||||
|
||||
char reply[8];
|
||||
int ret, i;
|
||||
|
||||
for (i = 0; vartab[i].var; i++) {
|
||||
int val;
|
||||
|
||||
ret = do_command(vartab[i].cmd, reply);
|
||||
if (ret < 8) {
|
||||
continue;
|
||||
}
|
||||
|
||||
val = (unsigned char)reply[6];
|
||||
val <<= 8;
|
||||
val += (unsigned char)reply[5];
|
||||
|
||||
dstate_setinfo(vartab[i].var, vartab[i].fmt, val * vartab[i].mult);
|
||||
}
|
||||
|
||||
status_init();
|
||||
|
||||
ret = do_command(cmd_bitfield1, reply);
|
||||
if (ret < 8) {
|
||||
upslogx(LOG_ERR, "Failed reading bitfield #1");
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
|
||||
if (reply[6] & (1<<0)) { /* ON_BATTERY */
|
||||
status_set("OB");
|
||||
} else {
|
||||
status_set("OL");
|
||||
}
|
||||
|
||||
ret = do_command(cmd_bitfield2, reply);
|
||||
if (ret < 8) {
|
||||
upslogx(LOG_ERR, "Failed reading bitfield #2");
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
|
||||
if (reply[5] & (1<<0)) { /* ON_BYPASS */
|
||||
status_set("BYPASS");
|
||||
}
|
||||
|
||||
if (reply[5] & (1<<5)) { /* REPLACE_BATTERY */
|
||||
status_set("RB");
|
||||
}
|
||||
|
||||
if (!(reply[5] & (1<<1))) { /* not BATTERY_CHARGED */
|
||||
status_set("CHRG");
|
||||
}
|
||||
|
||||
if (reply[5] & (1<<6)) { /* BOOST_ON */
|
||||
status_set("BOOST");
|
||||
}
|
||||
|
||||
if (reply[6] & (1<<1)) { /* BUCK_ON */
|
||||
status_set("TRIM");
|
||||
}
|
||||
|
||||
ret = do_command(cmd_bitfield3, reply);
|
||||
if (ret < 8) {
|
||||
upslogx(LOG_ERR, "Failed reading bitfield #3");
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
|
||||
if (reply[5] & (1<<0) ) { /* UPS_OVERLOAD */
|
||||
status_set("OVER");
|
||||
}
|
||||
|
||||
if (reply[5] & (1<<5) ) { /* LOW_BATTERY */
|
||||
status_set("LB");
|
||||
}
|
||||
|
||||
status_commit();
|
||||
|
||||
dstate_dataok();
|
||||
}
|
||||
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
/* replace with a proper shutdown function */
|
||||
fatalx(EXIT_FAILURE, "shutdown not supported");
|
||||
}
|
||||
|
||||
static int instcmd(const char *cmdname, const char *extra)
|
||||
{
|
||||
/*
|
||||
if (!strcasecmp(cmdname, "test.battery.stop")) {
|
||||
ser_send_buf(upsfd, ...);
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
*/
|
||||
upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
}
|
||||
|
||||
static int setvar(const char *varname, const char *val)
|
||||
{
|
||||
/*
|
||||
if (!strcasecmp(varname, "ups.test.interval")) {
|
||||
|
||||
ser_send_buf(upsfd, ...);
|
||||
return STAT_SET_HANDLED;
|
||||
}
|
||||
*/
|
||||
upslogx(LOG_NOTICE, "setvar: unknown variable [%s]", varname);
|
||||
return STAT_SET_UNKNOWN;
|
||||
}
|
||||
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
}
|
||||
|
||||
/* list flags and values that you want to receive via -x */
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
}
|
||||
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
upsfd = ser_open(device_path);
|
||||
ser_set_speed(upsfd, device_path, B2400);
|
||||
}
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
ser_close(upsfd, device_path);
|
||||
}
|
||||
571
drivers/main-hal.c
Normal file
571
drivers/main-hal.c
Normal file
|
|
@ -0,0 +1,571 @@
|
|||
/* main-hal.c - Network UPS Tools driver core for HAL
|
||||
|
||||
Copyright (C) 2006 Arnaud Quette <aquette.dev@gmail.com>
|
||||
Copyright (C) 1999 Russell Kroll <rkroll@exploits.org>
|
||||
|
||||
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:
|
||||
* -more cleanup
|
||||
* - dstate-hal: expose all data on org.freedesktop.NUT to prepare
|
||||
* a full HAL/DBus enabled nut (remove the need of {d,s}state layer
|
||||
* - use HAL logging functions
|
||||
*/
|
||||
|
||||
#include "main-hal.h"
|
||||
#include "dstate-hal.h"
|
||||
#include <hal/libhal.h>
|
||||
|
||||
#ifdef HAVE_POLKIT
|
||||
#include <libpolkit.h>
|
||||
#endif
|
||||
|
||||
/* HAL specific */
|
||||
extern LibHalContext *halctx;
|
||||
extern char *udi;
|
||||
|
||||
/* data which may be useful to the drivers */
|
||||
int upsfd = -1;
|
||||
char *device_path = NULL;
|
||||
const char *progname = NULL, *upsname = NULL,
|
||||
*device_name = NULL;
|
||||
|
||||
/* may be set by the driver to wake up while in dstate_poll_fds */
|
||||
int extrafd = -1;
|
||||
|
||||
/* for ser_open */
|
||||
int do_lock_port = 1;
|
||||
|
||||
/* set by the drivers */
|
||||
int experimental_driver = 0;
|
||||
int broken_driver = 0;
|
||||
|
||||
static vartab_t *vartab_h = NULL;
|
||||
|
||||
/* variables possibly set by the global part of ups.conf */
|
||||
unsigned int poll_interval = 2;
|
||||
static char *chroot_path = NULL;
|
||||
|
||||
/* signal handling */
|
||||
int exit_flag = 0;
|
||||
|
||||
/* everything else */
|
||||
static char *pidfn = NULL;
|
||||
GMainLoop *gmain;
|
||||
char *dbus_methods_introspection;
|
||||
|
||||
/* retrieve the value of variable <var> if possible */
|
||||
char *getval(const char *var)
|
||||
{
|
||||
vartab_t *tmp = vartab_h;
|
||||
|
||||
while (tmp) {
|
||||
if (!strcasecmp(tmp->var, var))
|
||||
return(tmp->val);
|
||||
tmp = tmp->next;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* see if <var> has been defined, even if no value has been given to it */
|
||||
int testvar(const char *var)
|
||||
{
|
||||
vartab_t *tmp = vartab_h;
|
||||
|
||||
while (tmp) {
|
||||
if (!strcasecmp(tmp->var, var))
|
||||
return tmp->found;
|
||||
tmp = tmp->next;
|
||||
}
|
||||
|
||||
return 0; /* not found */
|
||||
}
|
||||
|
||||
/* callback from driver - create the table for -x/conf entries */
|
||||
void addvar(int vartype, const char *name, const char *desc)
|
||||
{
|
||||
vartab_t *tmp, *last;
|
||||
|
||||
tmp = last = vartab_h;
|
||||
|
||||
while (tmp) {
|
||||
last = tmp;
|
||||
tmp = tmp->next;
|
||||
}
|
||||
|
||||
tmp = xmalloc(sizeof(vartab_t));
|
||||
|
||||
tmp->vartype = vartype;
|
||||
tmp->var = xstrdup(name);
|
||||
tmp->val = NULL;
|
||||
tmp->desc = xstrdup(desc);
|
||||
tmp->found = 0;
|
||||
tmp->next = NULL;
|
||||
|
||||
if (last)
|
||||
last->next = tmp;
|
||||
else
|
||||
vartab_h = tmp;
|
||||
}
|
||||
|
||||
static void vartab_free(void)
|
||||
{
|
||||
vartab_t *tmp, *next;
|
||||
|
||||
tmp = vartab_h;
|
||||
|
||||
while (tmp) {
|
||||
next = tmp->next;
|
||||
|
||||
free(tmp->var);
|
||||
free(tmp->val);
|
||||
free(tmp->desc);
|
||||
free(tmp);
|
||||
|
||||
tmp = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void exit_cleanup(int sig)
|
||||
{
|
||||
upsdebugx(2, "exit_cleanup(%i", sig);
|
||||
|
||||
exit_flag = sig;
|
||||
|
||||
upsdrv_cleanup();
|
||||
|
||||
free(chroot_path);
|
||||
free(device_path);
|
||||
|
||||
if (pidfn) {
|
||||
unlink(pidfn);
|
||||
free(pidfn);
|
||||
}
|
||||
|
||||
dstate_free();
|
||||
vartab_free();
|
||||
|
||||
/* break the main loop */
|
||||
g_main_loop_quit(gmain);
|
||||
}
|
||||
|
||||
static void setup_signals(void)
|
||||
{
|
||||
struct sigaction sa;
|
||||
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
|
||||
|
||||
sa.sa_handler = exit_cleanup;
|
||||
sigaction(SIGTERM, &sa, NULL);
|
||||
sigaction(SIGINT, &sa, NULL);
|
||||
sigaction(SIGQUIT, &sa, NULL);
|
||||
|
||||
/* sa.sa_handler = SIG_IGN;
|
||||
sigaction(SIGHUP, &sa, NULL);
|
||||
sigaction(SIGPIPE, &sa, NULL);*/
|
||||
}
|
||||
|
||||
/* (*GSourceFunc) wrapper */
|
||||
static gboolean update_data (gpointer data)
|
||||
{
|
||||
upsdrv_updateinfo();
|
||||
return (exit_flag == 0);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
DBusError dbus_error;
|
||||
DBusConnection *dbus_connection;
|
||||
struct passwd *new_uid = NULL;
|
||||
char *hal_debug_level;
|
||||
/* int i, do_forceshutdown = 0; */
|
||||
|
||||
if (experimental_driver) {
|
||||
printf("Warning: This is an experimental driver.\n");
|
||||
printf("Some features may not function correctly.\n\n");
|
||||
}
|
||||
|
||||
progname = xbasename(argv[0]);
|
||||
open_syslog(progname);
|
||||
|
||||
dbus_methods_introspection = xmalloc(1024);
|
||||
|
||||
/* Register the basic supported method (Shutdown)
|
||||
* Leave other methods registration to driver core calls to
|
||||
* dstate_addcmd(), at initinfo() time
|
||||
*/
|
||||
snprintf(dbus_methods_introspection, 1024, "%s",
|
||||
" <method name=\"Shutdown\">\n"
|
||||
/* " <arg name=\"shutdown_type\" direction=\"in\" type=\"s\"/>\n" */
|
||||
" <arg name=\"return_code\" direction=\"out\" type=\"i\"/>\n"
|
||||
" </method>\n");
|
||||
|
||||
/* initialise HAL and DBus interface*/
|
||||
halctx = NULL;
|
||||
udi = getenv ("UDI");
|
||||
if (udi == NULL) {
|
||||
fprintf(stderr, "Error: UDI is null.\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
dbus_error_init (&dbus_error);
|
||||
if ((halctx = libhal_ctx_init_direct (&dbus_error)) == NULL) {
|
||||
fprintf(stderr, "Error: can't initialise libhal.\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (dbus_error_is_set (&dbus_error))
|
||||
{
|
||||
fatalx(EXIT_FAILURE, "Error in context creation: %s\n", dbus_error.message);
|
||||
dbus_error_free (&dbus_error);
|
||||
}
|
||||
|
||||
if ((dbus_connection = libhal_ctx_get_dbus_connection(halctx)) == NULL) {
|
||||
fprintf(stderr, "Error: can't get DBus connection.\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* FIXME: rework HAL param interface! or get path/regex from UDI
|
||||
* Example:
|
||||
* /org/freedesktop/Hal/devices/usb_device_463_ffff_1H2E300AH
|
||||
* => linux.device_file = /dev/bus/usb/002/065
|
||||
*/
|
||||
/* FIXME: the naming should be abstracted to os.device_file! */
|
||||
device_path = xstrdup("auto");
|
||||
|
||||
/* FIXME: bridge debug/warning on HAL equivalent (need them
|
||||
* to externalize these in a lib... */
|
||||
hal_debug_level = getenv ("NUT_HAL_DEBUG");
|
||||
if (hal_debug_level == NULL)
|
||||
nut_debug_level = 0;
|
||||
else
|
||||
nut_debug_level = atoi(hal_debug_level);
|
||||
|
||||
/* Sleep 2 seconds to be able to view the device through usbfs! */
|
||||
sleep(2);
|
||||
|
||||
/* build the driver's extra (-x) variable table */
|
||||
upsdrv_makevartable();
|
||||
|
||||
/* Switch to the HAL user */
|
||||
new_uid = get_user_pwent(HAL_USER);
|
||||
become_user(new_uid);
|
||||
|
||||
/* Only switch to statepath if we're not powering off */
|
||||
/* This avoid case where ie /var is umounted */
|
||||
/* ?! Not needed for HAL !?
|
||||
if (!do_forceshutdown)
|
||||
if (chdir(dflt_statepath()))
|
||||
fatal_with_errno(EXIT_FAILURE, "Can't chdir to %s", dflt_statepath());
|
||||
*/
|
||||
setup_signals();
|
||||
|
||||
/* clear out callback handler data */
|
||||
memset(&upsh, '\0', sizeof(upsh));
|
||||
|
||||
upsdrv_initups();
|
||||
|
||||
#if 0
|
||||
if (do_forceshutdown)
|
||||
forceshutdown();
|
||||
#endif
|
||||
|
||||
/* get the supported data and commands before allowing connections */
|
||||
upsdrv_initinfo();
|
||||
upsdrv_updateinfo();
|
||||
|
||||
/* now we can start servicing requests */
|
||||
dstate_init(NULL, NULL);
|
||||
|
||||
/* Commit DBus methods */
|
||||
if (!libhal_device_claim_interface(halctx, udi, DBUS_INTERFACE,
|
||||
dbus_methods_introspection, &dbus_error)) {
|
||||
fprintf(stderr, "Cannot claim interface: %s\n", dbus_error.message);
|
||||
}
|
||||
else
|
||||
fprintf(stdout, "Claimed the following DBus interfaces on %s:\n%s",
|
||||
DBUS_INTERFACE, dbus_methods_introspection);
|
||||
|
||||
/* Complete DBus binding */
|
||||
dbus_connection_setup_with_g_main(dbus_connection, NULL);
|
||||
dbus_connection_add_filter(dbus_connection, dbus_filter_function, NULL, NULL);
|
||||
dbus_connection_set_exit_on_disconnect(dbus_connection, 0);
|
||||
|
||||
dbus_init_local();
|
||||
/* end of HAL init */
|
||||
|
||||
#if 0
|
||||
/* publish the top-level data: version number, driver name */
|
||||
dstate_setinfo("driver.version", "%s", UPS_VERSION);
|
||||
dstate_setinfo("driver.name", "%s", progname);
|
||||
|
||||
/* The poll_interval may have been changed from the default */
|
||||
dstate_setinfo("driver.parameter.pollinterval", "%d", poll_interval);
|
||||
|
||||
/* FIXME: needed? */
|
||||
if (nut_debug_level == 0) {
|
||||
background();
|
||||
writepid(pidfn);
|
||||
}
|
||||
#endif
|
||||
/* End HAL init */
|
||||
dbus_error_init (&dbus_error);
|
||||
if (!libhal_device_addon_is_ready (halctx, udi, &dbus_error)) {
|
||||
fprintf(stderr, "Error (libhal): device addon is not ready\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* add a timer for data update */
|
||||
#ifdef HAVE_GLIB_2_14
|
||||
g_timeout_add_seconds (poll_interval,
|
||||
#else
|
||||
g_timeout_add (1000 * poll_interval, /* seconds */
|
||||
#endif
|
||||
(GSourceFunc)update_data,
|
||||
NULL);
|
||||
|
||||
/* setup and run the main loop */
|
||||
gmain = g_main_loop_new(NULL, FALSE);
|
||||
g_main_loop_run(gmain);
|
||||
|
||||
/* reached upon addon exit */
|
||||
upslogx(LOG_INFO, "Signal %d: exiting", exit_flag);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/********************************************************************
|
||||
* DBus interface functions and data
|
||||
*******************************************************************/
|
||||
|
||||
static DBusHandlerResult dbus_filter_function_local(DBusConnection *connection,
|
||||
DBusMessage *message,
|
||||
void *user_data)
|
||||
{
|
||||
if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) {
|
||||
upsdebugx(1, "DBus daemon disconnected. Trying to reconnect...");
|
||||
dbus_connection_unref(connection);
|
||||
g_timeout_add(5000, (GSourceFunc)dbus_init_local, NULL);
|
||||
}
|
||||
return DBUS_HANDLER_RESULT_HANDLED;
|
||||
}
|
||||
|
||||
/* returns FALSE on success because it's used as a callback */
|
||||
gboolean dbus_init_local(void)
|
||||
{
|
||||
DBusConnection *dbus_connection;
|
||||
DBusError dbus_error;
|
||||
|
||||
dbus_error_init(&dbus_error);
|
||||
|
||||
dbus_connection = dbus_bus_get(DBUS_BUS_SYSTEM, &dbus_error);
|
||||
if (dbus_error_is_set(&dbus_error)) {
|
||||
upsdebugx(1, "Cannot get D-Bus connection");
|
||||
/* dbus_error_free (&dbus_error); */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
dbus_connection_setup_with_g_main(dbus_connection, NULL);
|
||||
dbus_connection_add_filter(dbus_connection, dbus_filter_function_local,
|
||||
NULL, NULL);
|
||||
dbus_connection_set_exit_on_disconnect(dbus_connection, 0);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
#ifdef HAVE_POLKIT
|
||||
/**
|
||||
* dbus_is_privileged:
|
||||
* @connection: connection to D-Bus
|
||||
* @message: Message
|
||||
* @error: the error
|
||||
*
|
||||
* Returns: TRUE if the caller is privileged
|
||||
*
|
||||
* checks if caller of message possesses the CPUFREQ_POLKIT_PRIVILGE
|
||||
*/
|
||||
static gboolean
|
||||
dbus_is_privileged (DBusConnection *connection, DBusMessage *message, DBusError *error)
|
||||
{
|
||||
gboolean ret;
|
||||
char *polkit_result;
|
||||
const char *invoked_by_syscon_name;
|
||||
|
||||
ret = FALSE;
|
||||
polkit_result = NULL;
|
||||
/* FIXME: CPUFREQ_POLKIT_PRIVILEGE, CPUFREQ_ERROR_GENERAL */
|
||||
invoked_by_syscon_name = dbus_message_get_sender (message);
|
||||
|
||||
polkit_result = libhal_device_is_caller_privileged (halctx,
|
||||
udi,
|
||||
CPUFREQ_POLKIT_PRIVILEGE,
|
||||
invoked_by_syscon_name,
|
||||
error);
|
||||
if (polkit_result == NULL) {
|
||||
dbus_raise_error (connection, message, CPUFREQ_ERROR_GENERAL,
|
||||
"Cannot determine if caller is privileged");
|
||||
}
|
||||
else {
|
||||
if (strcmp (polkit_result, "yes") != 0) {
|
||||
|
||||
dbus_raise_error (connection, message,
|
||||
"org.freedesktop.Hal.Device.PermissionDeniedByPolicy",
|
||||
"%s %s <-- (action, result)",
|
||||
CPUFREQ_POLKIT_PRIVILEGE, polkit_result);
|
||||
}
|
||||
else
|
||||
ret = TRUE;
|
||||
}
|
||||
|
||||
if (polkit_result != NULL)
|
||||
libhal_free_string (polkit_result);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* dbus_send_reply:
|
||||
* @connection: connection to D-Bus
|
||||
* @message: Message
|
||||
* @type: the type of data param
|
||||
* @data: data to send
|
||||
*
|
||||
* Returns: TRUE/FALSE
|
||||
*
|
||||
* sends a reply to message with the given data and its dbus_type
|
||||
*/
|
||||
static gboolean dbus_send_reply(DBusConnection *connection, DBusMessage *message,
|
||||
int dbus_type, void *data)
|
||||
{
|
||||
DBusMessage *reply;
|
||||
|
||||
if ((reply = dbus_message_new_method_return(message)) == NULL) {
|
||||
upslogx(LOG_WARNING, "Could not allocate memory for the DBus reply");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (data != NULL)
|
||||
dbus_message_append_args(reply, dbus_type, data, DBUS_TYPE_INVALID);
|
||||
|
||||
if (!dbus_connection_send(connection, reply, NULL)) {
|
||||
upslogx(LOG_WARNING, "Could not sent reply");
|
||||
return FALSE;
|
||||
}
|
||||
dbus_connection_flush(connection);
|
||||
dbus_message_unref(reply);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* dbus_get_argument:
|
||||
* @connection: connection to D-Bus
|
||||
* @message: Message
|
||||
* @dbus_error: the D-Bus error
|
||||
* @type: the type of arg param
|
||||
* @arg: the value to get from the message
|
||||
*
|
||||
* Returns: TRUE/FALSE
|
||||
*
|
||||
* gets one argument from message with the given dbus_type and stores it in arg
|
||||
*/
|
||||
static gboolean dbus_get_argument(DBusConnection *connection, DBusMessage *message,
|
||||
DBusError *dbus_error, int dbus_type, void *arg)
|
||||
{
|
||||
dbus_message_get_args(message, dbus_error, dbus_type, arg,
|
||||
DBUS_TYPE_INVALID);
|
||||
if (dbus_error_is_set(dbus_error)) {
|
||||
upslogx(LOG_WARNING, "Could not get argument of DBus message: %s",
|
||||
dbus_error->message);
|
||||
dbus_error_free(dbus_error);
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* dbus_filter_function:
|
||||
* @connection: connection to D-Bus
|
||||
* @message: message
|
||||
* @user_data: pointer to the data
|
||||
*
|
||||
* Returns: the result
|
||||
*
|
||||
* @raises UnknownMethod
|
||||
*
|
||||
* D-Bus filter function
|
||||
*/
|
||||
DBusHandlerResult dbus_filter_function(DBusConnection *connection,
|
||||
DBusMessage *message,
|
||||
void *user_data)
|
||||
{
|
||||
DBusError dbus_error;
|
||||
int retcode = -1;
|
||||
const char *member = dbus_message_get_member(message);
|
||||
const char *path = dbus_message_get_path(message);
|
||||
/* int ret = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; */
|
||||
|
||||
/* upsdebugx(2, "Received DBus message with member %s path %s", member, path); */
|
||||
fprintf(stdout, "Received DBus message with member %s on path %s\n", member, path);
|
||||
|
||||
dbus_error_init(&dbus_error);
|
||||
if (dbus_error_is_set (&dbus_error))
|
||||
{
|
||||
fprintf (stderr, "an error occurred: %s\n", dbus_error.message);
|
||||
/* dbus_error_free (&dbus_error); */
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef HAVE_POLKIT
|
||||
if (!dbus_is_privileged(connection, message, &dbus_error))
|
||||
return DBUS_HANDLER_RESULT_HANDLED;
|
||||
#endif
|
||||
|
||||
if (dbus_message_is_method_call(message, DBUS_INTERFACE, "Shutdown")) {
|
||||
|
||||
fprintf(stdout, "executing Shutdown\n");
|
||||
upsdrv_shutdown();
|
||||
dbus_send_reply(connection, message, DBUS_TYPE_INVALID, NULL);
|
||||
|
||||
} else if (dbus_message_is_method_call(message,
|
||||
DBUS_INTERFACE, "SetBeeper")) {
|
||||
fprintf(stdout, "executing SetBeeper\n");
|
||||
gboolean b_enable;
|
||||
if (!dbus_get_argument(connection, message, &dbus_error,
|
||||
DBUS_TYPE_BOOLEAN, &b_enable)) {
|
||||
fprintf(stderr, "Error receiving boolean argument\n");
|
||||
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||
}
|
||||
fprintf(stdout, "Received argument: %s\n", (b_enable==TRUE)?"true":"false");
|
||||
|
||||
if (b_enable==TRUE) {
|
||||
if (upsh.instcmd("beeper.enable", NULL) != STAT_INSTCMD_HANDLED) {
|
||||
dbus_send_reply(connection, message, DBUS_TYPE_INT32, &retcode);
|
||||
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (upsh.instcmd("beeper.disable", NULL) != STAT_INSTCMD_HANDLED) {
|
||||
dbus_send_reply(connection, message, DBUS_TYPE_INT32, &retcode);
|
||||
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||
}
|
||||
}
|
||||
dbus_send_reply(connection, message, DBUS_TYPE_INVALID, NULL);
|
||||
return DBUS_HANDLER_RESULT_HANDLED;
|
||||
}
|
||||
51
drivers/main-hal.h
Normal file
51
drivers/main-hal.h
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#include "common.h"
|
||||
#include "config.h"
|
||||
/* #include "upsconf.h" */
|
||||
#include "dstate.h"
|
||||
#include "extstate.h"
|
||||
|
||||
/* public functions & variables from main.c */
|
||||
extern const char *progname;
|
||||
extern char *device_path;
|
||||
extern const char *device_name;
|
||||
extern int upsfd, extrafd, broken_driver, experimental_driver, exit_flag;
|
||||
extern unsigned int poll_interval;
|
||||
|
||||
/* functions & variables required in each driver */
|
||||
void upsdrv_initups(void); /* open connection to UPS, fail if not found */
|
||||
void upsdrv_initinfo(void); /* prep data, settings for UPS monitoring */
|
||||
void upsdrv_updateinfo(void); /* update state data if possible */
|
||||
void upsdrv_shutdown(void); /* make the UPS power off the load */
|
||||
void upsdrv_help(void); /* tack on anything useful for the -h text */
|
||||
void upsdrv_banner(void); /* print your version information */
|
||||
void upsdrv_cleanup(void); /* free any resources before shutdown */
|
||||
|
||||
/* --- details for the variable/value sharing --- */
|
||||
|
||||
/* main calls this driver function - it needs to call addvar */
|
||||
void upsdrv_makevartable(void);
|
||||
|
||||
/* retrieve the value of variable <var> if possible */
|
||||
char *getval(const char *var);
|
||||
|
||||
/* see if <var> has been defined, even if no value has been given to it */
|
||||
int testvar(const char *var);
|
||||
|
||||
/* extended variable table - used for -x defines/flags */
|
||||
typedef struct {
|
||||
int vartype; /* VAR_* value, below */
|
||||
char *var; /* left side of =, or whole word if none */
|
||||
char *val; /* right side of = */
|
||||
char *desc; /* 40 character description for -h text */
|
||||
int found; /* set once encountered, for testvar() */
|
||||
void *next;
|
||||
} vartab_t;
|
||||
|
||||
/* flags to define types in the vartab */
|
||||
|
||||
#define VAR_FLAG 0x0001 /* argument is a flag (no value needed) */
|
||||
#define VAR_VALUE 0x0002 /* argument requires a value setting */
|
||||
#define VAR_SENSITIVE 0x0004 /* do not publish in driver.parameter */
|
||||
|
||||
/* callback from driver - create the table for future -x entries */
|
||||
void addvar(int vartype, const char *name, const char *desc);
|
||||
636
drivers/main.c
Normal file
636
drivers/main.c
Normal file
|
|
@ -0,0 +1,636 @@
|
|||
/* main.c - Network UPS Tools driver core
|
||||
|
||||
Copyright (C) 1999 Russell Kroll <rkroll@exploits.org>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "dstate.h"
|
||||
|
||||
/* data which may be useful to the drivers */
|
||||
int upsfd = -1;
|
||||
char *device_path = NULL;
|
||||
const char *progname = NULL, *upsname = NULL, *device_name = NULL;
|
||||
|
||||
/* may be set by the driver to wake up while in dstate_poll_fds */
|
||||
int extrafd = -1;
|
||||
|
||||
/* for ser_open */
|
||||
int do_lock_port = 1;
|
||||
|
||||
/* for detecting -a values that don't match anything */
|
||||
static int upsname_found = 0;
|
||||
|
||||
static vartab_t *vartab_h = NULL;
|
||||
|
||||
/* variables possibly set by the global part of ups.conf */
|
||||
unsigned int poll_interval = 2;
|
||||
static char *chroot_path = NULL, *user = NULL;
|
||||
|
||||
/* signal handling */
|
||||
int exit_flag = 0;
|
||||
|
||||
/* everything else */
|
||||
static char *pidfn = NULL;
|
||||
|
||||
/* print the driver banner */
|
||||
void upsdrv_banner (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
printf("Network UPS Tools - %s %s (%s)\n", upsdrv_info.name, upsdrv_info.version, UPS_VERSION);
|
||||
|
||||
/* process sub driver(s) information */
|
||||
for (i = 0; upsdrv_info.subdrv_info[i]; i++) {
|
||||
|
||||
if (!upsdrv_info.subdrv_info[i]->name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!upsdrv_info.subdrv_info[i]->version) {
|
||||
continue;
|
||||
}
|
||||
|
||||
printf("%s %s\n", upsdrv_info.subdrv_info[i]->name,
|
||||
upsdrv_info.subdrv_info[i]->version);
|
||||
}
|
||||
}
|
||||
|
||||
/* power down the attached load immediately */
|
||||
static void forceshutdown(void)
|
||||
{
|
||||
upslogx(LOG_NOTICE, "Initiating UPS shutdown");
|
||||
|
||||
/* the driver must not block in this function */
|
||||
upsdrv_shutdown();
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/* this function only prints the usage message; it does not call exit() */
|
||||
static void help_msg(void)
|
||||
{
|
||||
vartab_t *tmp;
|
||||
|
||||
printf("\nusage: %s -a <id> [OPTIONS]\n", progname);
|
||||
|
||||
printf(" -a <id> - autoconfig using ups.conf section <id>\n");
|
||||
printf(" - note: -x after -a overrides ups.conf settings\n\n");
|
||||
|
||||
printf(" -V - print version, then exit\n");
|
||||
printf(" -L - print parseable list of driver variables\n");
|
||||
printf(" -D - raise debugging level\n");
|
||||
printf(" -h - display this help\n");
|
||||
printf(" -k - force shutdown\n");
|
||||
printf(" -i <int> - poll interval\n");
|
||||
printf(" -r <dir> - chroot to <dir>\n");
|
||||
printf(" -u <user> - switch to <user> (if started as root)\n");
|
||||
printf(" -x <var>=<val> - set driver variable <var> to <val>\n");
|
||||
printf(" - example: -x cable=940-0095B\n\n");
|
||||
|
||||
if (vartab_h) {
|
||||
tmp = vartab_h;
|
||||
|
||||
printf("Acceptable values for -x or ups.conf in this driver:\n\n");
|
||||
|
||||
while (tmp) {
|
||||
if (tmp->vartype == VAR_VALUE)
|
||||
printf("%40s : -x %s=<value>\n",
|
||||
tmp->desc, tmp->var);
|
||||
else
|
||||
printf("%40s : -x %s\n", tmp->desc, tmp->var);
|
||||
tmp = tmp->next;
|
||||
}
|
||||
}
|
||||
|
||||
upsdrv_help();
|
||||
}
|
||||
|
||||
/* store these in dstate as driver.(parameter|flag) */
|
||||
static void dparam_setinfo(const char *var, const char *val)
|
||||
{
|
||||
char vtmp[SMALLBUF];
|
||||
|
||||
/* store these in dstate for debugging and other help */
|
||||
if (val) {
|
||||
snprintf(vtmp, sizeof(vtmp), "driver.parameter.%s", var);
|
||||
dstate_setinfo(vtmp, "%s", val);
|
||||
return;
|
||||
}
|
||||
|
||||
/* no value = flag */
|
||||
|
||||
snprintf(vtmp, sizeof(vtmp), "driver.flag.%s", var);
|
||||
dstate_setinfo(vtmp, "enabled");
|
||||
}
|
||||
|
||||
/* cram var [= <val>] data into storage */
|
||||
static void storeval(const char *var, char *val)
|
||||
{
|
||||
vartab_t *tmp, *last;
|
||||
|
||||
if (!strncasecmp(var, "override.", 9)) {
|
||||
dstate_setinfo(var+9, "%s", val);
|
||||
dstate_setflags(var+9, ST_FLAG_IMMUTABLE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!strncasecmp(var, "default.", 8)) {
|
||||
dstate_setinfo(var+8, "%s", val);
|
||||
return;
|
||||
}
|
||||
|
||||
tmp = last = vartab_h;
|
||||
|
||||
while (tmp) {
|
||||
last = tmp;
|
||||
|
||||
/* sanity check */
|
||||
if (!tmp->var) {
|
||||
tmp = tmp->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* later definitions overwrite earlier ones */
|
||||
if (!strcasecmp(tmp->var, var)) {
|
||||
free(tmp->val);
|
||||
|
||||
if (val)
|
||||
tmp->val = xstrdup(val);
|
||||
|
||||
/* don't keep things like SNMP community strings */
|
||||
if ((tmp->vartype & VAR_SENSITIVE) == 0)
|
||||
dparam_setinfo(var, val);
|
||||
|
||||
tmp->found = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
tmp = tmp->next;
|
||||
}
|
||||
|
||||
/* try to help them out */
|
||||
printf("\nFatal error: '%s' is not a valid %s for this driver.\n", var,
|
||||
val ? "variable name" : "flag");
|
||||
printf("\n");
|
||||
printf("Look in the man page or call this driver with -h for a list of\n");
|
||||
printf("valid variable names and flags.\n");
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/* retrieve the value of variable <var> if possible */
|
||||
char *getval(const char *var)
|
||||
{
|
||||
vartab_t *tmp = vartab_h;
|
||||
|
||||
while (tmp) {
|
||||
if (!strcasecmp(tmp->var, var))
|
||||
return(tmp->val);
|
||||
tmp = tmp->next;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* see if <var> has been defined, even if no value has been given to it */
|
||||
int testvar(const char *var)
|
||||
{
|
||||
vartab_t *tmp = vartab_h;
|
||||
|
||||
while (tmp) {
|
||||
if (!strcasecmp(tmp->var, var))
|
||||
return tmp->found;
|
||||
tmp = tmp->next;
|
||||
}
|
||||
|
||||
return 0; /* not found */
|
||||
}
|
||||
|
||||
/* callback from driver - create the table for -x/conf entries */
|
||||
void addvar(int vartype, const char *name, const char *desc)
|
||||
{
|
||||
vartab_t *tmp, *last;
|
||||
|
||||
tmp = last = vartab_h;
|
||||
|
||||
while (tmp) {
|
||||
last = tmp;
|
||||
tmp = tmp->next;
|
||||
}
|
||||
|
||||
tmp = xmalloc(sizeof(vartab_t));
|
||||
|
||||
tmp->vartype = vartype;
|
||||
tmp->var = xstrdup(name);
|
||||
tmp->val = NULL;
|
||||
tmp->desc = xstrdup(desc);
|
||||
tmp->found = 0;
|
||||
tmp->next = NULL;
|
||||
|
||||
if (last)
|
||||
last->next = tmp;
|
||||
else
|
||||
vartab_h = tmp;
|
||||
}
|
||||
|
||||
/* handle -x / ups.conf config details that are for this part of the code */
|
||||
static int main_arg(char *var, char *val)
|
||||
{
|
||||
/* flags for main: just 'nolock' for now */
|
||||
|
||||
if (!strcmp(var, "nolock")) {
|
||||
do_lock_port = 0;
|
||||
dstate_setinfo("driver.flag.nolock", "enabled");
|
||||
return 1; /* handled */
|
||||
}
|
||||
|
||||
/* any other flags are for the driver code */
|
||||
if (!val)
|
||||
return 0;
|
||||
|
||||
/* variables for main: port */
|
||||
|
||||
if (!strcmp(var, "port")) {
|
||||
device_path = xstrdup(val);
|
||||
device_name = xbasename(device_path);
|
||||
dstate_setinfo("driver.parameter.port", "%s", val);
|
||||
return 1; /* handled */
|
||||
}
|
||||
|
||||
if (!strcmp(var, "sddelay")) {
|
||||
upslogx(LOG_INFO, "Obsolete value sddelay found in ups.conf");
|
||||
return 1; /* handled */
|
||||
}
|
||||
|
||||
/* only for upsdrvctl - ignored here */
|
||||
if (!strcmp(var, "sdorder"))
|
||||
return 1; /* handled */
|
||||
|
||||
/* only for upsd (at the moment) - ignored here */
|
||||
if (!strcmp(var, "desc"))
|
||||
return 1; /* handled */
|
||||
|
||||
return 0; /* unhandled, pass it through to the driver */
|
||||
}
|
||||
|
||||
static void do_global_args(const char *var, const char *val)
|
||||
{
|
||||
if (!strcmp(var, "pollinterval")) {
|
||||
poll_interval = atoi(val);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!strcmp(var, "chroot")) {
|
||||
free(chroot_path);
|
||||
chroot_path = xstrdup(val);
|
||||
}
|
||||
|
||||
if (!strcmp(var, "user")) {
|
||||
free(user);
|
||||
user = xstrdup(val);
|
||||
}
|
||||
|
||||
|
||||
/* unrecognized */
|
||||
}
|
||||
|
||||
void do_upsconf_args(char *confupsname, char *var, char *val)
|
||||
{
|
||||
char tmp[SMALLBUF];
|
||||
|
||||
/* handle global declarations */
|
||||
if (!confupsname) {
|
||||
do_global_args(var, val);
|
||||
return;
|
||||
}
|
||||
|
||||
/* no match = not for us */
|
||||
if (strcmp(confupsname, upsname) != 0)
|
||||
return;
|
||||
|
||||
upsname_found = 1;
|
||||
|
||||
if (main_arg(var, val))
|
||||
return;
|
||||
|
||||
/* flags (no =) now get passed to the driver-level stuff */
|
||||
if (!val) {
|
||||
|
||||
/* also store this, but it's a bit different */
|
||||
snprintf(tmp, sizeof(tmp), "driver.flag.%s", var);
|
||||
dstate_setinfo(tmp, "enabled");
|
||||
|
||||
storeval(var, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
/* don't let the user shoot themselves in the foot */
|
||||
if (!strcmp(var, "driver")) {
|
||||
if (strcmp(val, progname) != 0)
|
||||
fatalx(EXIT_FAILURE, "Error: UPS [%s] is for driver %s, but I'm %s!\n",
|
||||
confupsname, val, progname);
|
||||
return;
|
||||
}
|
||||
|
||||
/* allow per-driver overrides of the global setting */
|
||||
if (!strcmp(var, "pollinterval")) {
|
||||
poll_interval = atoi(val);
|
||||
return;
|
||||
}
|
||||
|
||||
/* everything else must be for the driver */
|
||||
|
||||
storeval(var, val);
|
||||
}
|
||||
|
||||
/* split -x foo=bar into 'foo' and 'bar' */
|
||||
static void splitxarg(char *inbuf)
|
||||
{
|
||||
char *eqptr, *val, *buf;
|
||||
|
||||
/* make our own copy - avoid changing argv */
|
||||
buf = xstrdup(inbuf);
|
||||
|
||||
eqptr = strchr(buf, '=');
|
||||
|
||||
if (!eqptr)
|
||||
val = NULL;
|
||||
else {
|
||||
*eqptr++ = '\0';
|
||||
val = eqptr;
|
||||
}
|
||||
|
||||
/* see if main handles this first */
|
||||
if (main_arg(buf, val))
|
||||
return;
|
||||
|
||||
/* otherwise store it for later */
|
||||
storeval(buf, val);
|
||||
}
|
||||
|
||||
/* dump the list from the vartable for external parsers */
|
||||
static void listxarg(void)
|
||||
{
|
||||
vartab_t *tmp;
|
||||
|
||||
tmp = vartab_h;
|
||||
|
||||
if (!tmp)
|
||||
return;
|
||||
|
||||
while (tmp) {
|
||||
|
||||
switch (tmp->vartype) {
|
||||
case VAR_VALUE: printf("VALUE"); break;
|
||||
case VAR_FLAG: printf("FLAG"); break;
|
||||
default: printf("UNKNOWN"); break;
|
||||
}
|
||||
|
||||
printf(" %s \"%s\"\n", tmp->var, tmp->desc);
|
||||
|
||||
tmp = tmp->next;
|
||||
}
|
||||
}
|
||||
|
||||
static void vartab_free(void)
|
||||
{
|
||||
vartab_t *tmp, *next;
|
||||
|
||||
tmp = vartab_h;
|
||||
|
||||
while (tmp) {
|
||||
next = tmp->next;
|
||||
|
||||
free(tmp->var);
|
||||
free(tmp->val);
|
||||
free(tmp->desc);
|
||||
free(tmp);
|
||||
|
||||
tmp = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void exit_cleanup(void)
|
||||
{
|
||||
free(chroot_path);
|
||||
free(device_path);
|
||||
free(user);
|
||||
|
||||
if (pidfn) {
|
||||
unlink(pidfn);
|
||||
free(pidfn);
|
||||
}
|
||||
|
||||
dstate_free();
|
||||
vartab_free();
|
||||
}
|
||||
|
||||
static void set_exit_flag(int sig)
|
||||
{
|
||||
exit_flag = sig;
|
||||
}
|
||||
|
||||
static void setup_signals(void)
|
||||
{
|
||||
struct sigaction sa;
|
||||
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = 0;
|
||||
|
||||
sa.sa_handler = set_exit_flag;
|
||||
sigaction(SIGTERM, &sa, NULL);
|
||||
sigaction(SIGINT, &sa, NULL);
|
||||
sigaction(SIGQUIT, &sa, NULL);
|
||||
|
||||
sa.sa_handler = SIG_IGN;
|
||||
sigaction(SIGHUP, &sa, NULL);
|
||||
sigaction(SIGPIPE, &sa, NULL);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct passwd *new_uid = NULL;
|
||||
int i, do_forceshutdown = 0;
|
||||
|
||||
atexit(exit_cleanup);
|
||||
|
||||
/* pick up a default from configure --with-user */
|
||||
user = xstrdup(RUN_AS_USER); /* xstrdup: this gets freed at exit */
|
||||
|
||||
progname = xbasename(argv[0]);
|
||||
open_syslog(progname);
|
||||
|
||||
upsdrv_banner();
|
||||
|
||||
if (upsdrv_info.status == DRV_EXPERIMENTAL) {
|
||||
printf("Warning: This is an experimental driver.\n");
|
||||
printf("Some features may not function correctly.\n\n");
|
||||
}
|
||||
|
||||
/* build the driver's extra (-x) variable table */
|
||||
upsdrv_makevartable();
|
||||
|
||||
while ((i = getopt(argc, argv, "+a:kDhx:Lr:u:Vi:")) != -1) {
|
||||
switch (i) {
|
||||
case 'a':
|
||||
upsname = optarg;
|
||||
|
||||
read_upsconf();
|
||||
|
||||
if (!upsname_found)
|
||||
fatalx(EXIT_FAILURE, "Error: Section %s not found in ups.conf",
|
||||
optarg);
|
||||
break;
|
||||
case 'D':
|
||||
nut_debug_level++;
|
||||
break;
|
||||
case 'i':
|
||||
poll_interval = atoi(optarg);
|
||||
break;
|
||||
case 'k':
|
||||
do_lock_port = 0;
|
||||
do_forceshutdown = 1;
|
||||
break;
|
||||
case 'L':
|
||||
listxarg();
|
||||
exit(EXIT_SUCCESS);
|
||||
case 'r':
|
||||
chroot_path = xstrdup(optarg);
|
||||
break;
|
||||
case 'u':
|
||||
user = xstrdup(optarg);
|
||||
break;
|
||||
case 'V':
|
||||
/* already printed the banner, so exit */
|
||||
exit(EXIT_SUCCESS);
|
||||
case 'x':
|
||||
splitxarg(optarg);
|
||||
break;
|
||||
case 'h':
|
||||
help_msg();
|
||||
exit(EXIT_SUCCESS);
|
||||
default:
|
||||
fatalx(EXIT_FAILURE,
|
||||
"Error: unknown option -%c. Try -h for help.", i);
|
||||
}
|
||||
}
|
||||
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
if (argc > 0) {
|
||||
fatalx(EXIT_FAILURE,
|
||||
"Error: too many non-option arguments. Try -h for help.");
|
||||
}
|
||||
|
||||
if (!upsname_found) {
|
||||
fatalx(EXIT_FAILURE,
|
||||
"Error: specifying '-a id' is now mandatory. Try -h for help.");
|
||||
}
|
||||
|
||||
/* we need to get the port from somewhere */
|
||||
if (!device_path) {
|
||||
fatalx(EXIT_FAILURE,
|
||||
"Error: you must specify a port name in ups.conf. Try -h for help.");
|
||||
}
|
||||
|
||||
pidfn = xmalloc(SMALLBUF);
|
||||
|
||||
snprintf(pidfn, SMALLBUF, "%s/%s-%s.pid", altpidpath(), progname, upsname);
|
||||
|
||||
upsdebugx(1, "debug level is '%d'", nut_debug_level);
|
||||
|
||||
new_uid = get_user_pwent(user);
|
||||
|
||||
if (chroot_path)
|
||||
chroot_start(chroot_path);
|
||||
|
||||
become_user(new_uid);
|
||||
|
||||
/* Only switch to statepath if we're not powering off */
|
||||
/* This avoid case where ie /var is umounted */
|
||||
if ((!do_forceshutdown) && (chdir(dflt_statepath())))
|
||||
fatal_with_errno(EXIT_FAILURE, "Can't chdir to %s", dflt_statepath());
|
||||
|
||||
setup_signals();
|
||||
|
||||
/* clear out callback handler data */
|
||||
memset(&upsh, '\0', sizeof(upsh));
|
||||
|
||||
upsdrv_initups();
|
||||
|
||||
/* UPS is detected now, cleanup upon exit */
|
||||
atexit(upsdrv_cleanup);
|
||||
|
||||
/* now see if things are very wrong out there */
|
||||
if (upsdrv_info.status == DRV_BROKEN) {
|
||||
fatalx(EXIT_FAILURE, "Fatal error: broken driver. It probably needs to be converted.\n");
|
||||
}
|
||||
|
||||
if (do_forceshutdown)
|
||||
forceshutdown();
|
||||
|
||||
/* note: device.type is set early to be overriden by the driver
|
||||
* when its a pdu! */
|
||||
dstate_setinfo("device.type", "ups");
|
||||
|
||||
/* publish the top-level data: version number, driver name */
|
||||
dstate_setinfo("driver.version", "%s", UPS_VERSION);
|
||||
dstate_setinfo("driver.version.internal", "%s", upsdrv_info.version);
|
||||
dstate_setinfo("driver.name", "%s", progname);
|
||||
|
||||
/* get the base data established before allowing connections */
|
||||
upsdrv_initinfo();
|
||||
upsdrv_updateinfo();
|
||||
|
||||
/* now we can start servicing requests */
|
||||
dstate_init(progname, upsname);
|
||||
|
||||
/* The poll_interval may have been changed from the default */
|
||||
dstate_setinfo("driver.parameter.pollinterval", "%d", poll_interval);
|
||||
|
||||
/* remap the device.* info from ups.* for the transition period */
|
||||
if (dstate_getinfo("ups.mfr") != NULL)
|
||||
dstate_setinfo("device.mfr", "%s", dstate_getinfo("ups.mfr"));
|
||||
if (dstate_getinfo("ups.model") != NULL)
|
||||
dstate_setinfo("device.model", "%s", dstate_getinfo("ups.model"));
|
||||
if (dstate_getinfo("ups.serial") != NULL)
|
||||
dstate_setinfo("device.serial", "%s", dstate_getinfo("ups.serial"));
|
||||
|
||||
if (nut_debug_level == 0) {
|
||||
background();
|
||||
writepid(pidfn);
|
||||
}
|
||||
|
||||
while (!exit_flag) {
|
||||
|
||||
struct timeval timeout;
|
||||
|
||||
gettimeofday(&timeout, NULL);
|
||||
timeout.tv_sec += poll_interval;
|
||||
|
||||
upsdrv_updateinfo();
|
||||
|
||||
while (!dstate_poll_fds(timeout, extrafd) && !exit_flag) {
|
||||
/* repeat until time is up or extrafd has data */
|
||||
}
|
||||
}
|
||||
|
||||
/* if we get here, the exit flag was set by a signal handler */
|
||||
upslogx(LOG_INFO, "Signal %d: exiting", exit_flag);
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
83
drivers/main.h
Normal file
83
drivers/main.h
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
#ifndef MAIN_H
|
||||
#define MAIN_H
|
||||
|
||||
#include "common.h"
|
||||
#include "upsconf.h"
|
||||
#include "dstate.h"
|
||||
#include "extstate.h"
|
||||
|
||||
/* public functions & variables from main.c */
|
||||
extern const char *progname, *upsname, *device_name;
|
||||
extern char *device_path;
|
||||
extern int upsfd, extrafd, broken_driver, experimental_driver, do_lock_port, exit_flag;
|
||||
extern unsigned int poll_interval;
|
||||
|
||||
/* functions & variables required in each driver */
|
||||
void upsdrv_initups(void); /* open connection to UPS, fail if not found */
|
||||
void upsdrv_initinfo(void); /* prep data, settings for UPS monitoring */
|
||||
void upsdrv_updateinfo(void); /* update state data if possible */
|
||||
void upsdrv_shutdown(void); /* make the UPS power off the load */
|
||||
void upsdrv_help(void); /* tack on anything useful for the -h text */
|
||||
void upsdrv_banner(void); /* print your version information */
|
||||
void upsdrv_cleanup(void); /* free any resources before shutdown */
|
||||
|
||||
/* --- details for the variable/value sharing --- */
|
||||
|
||||
/* main calls this driver function - it needs to call addvar */
|
||||
void upsdrv_makevartable(void);
|
||||
|
||||
/* retrieve the value of variable <var> if possible */
|
||||
char *getval(const char *var);
|
||||
|
||||
/* see if <var> has been defined, even if no value has been given to it */
|
||||
int testvar(const char *var);
|
||||
|
||||
/* extended variable table - used for -x defines/flags */
|
||||
struct vartab_s {
|
||||
int vartype; /* VAR_* value, below */
|
||||
char *var; /* left side of =, or whole word if none */
|
||||
char *val; /* right side of = */
|
||||
char *desc; /* 40 character description for -h text */
|
||||
int found; /* set once encountered, for testvar() */
|
||||
struct vartab_s *next;
|
||||
};
|
||||
|
||||
typedef struct vartab_s vartab_t;
|
||||
|
||||
/* flags to define types in the vartab */
|
||||
|
||||
#define VAR_FLAG 0x0001 /* argument is a flag (no value needed) */
|
||||
#define VAR_VALUE 0x0002 /* argument requires a value setting */
|
||||
#define VAR_SENSITIVE 0x0004 /* do not publish in driver.parameter */
|
||||
|
||||
/* callback from driver - create the table for future -x entries */
|
||||
void addvar(int vartype, const char *name, const char *desc);
|
||||
|
||||
/* subdriver description structure */
|
||||
struct upsdrv_info_s {
|
||||
const char *name; /* driver full name, for banner printing, ... */
|
||||
const char *version; /* driver version */
|
||||
const char *authors; /* authors name */
|
||||
const int status; /* driver development status */
|
||||
struct upsdrv_info_s *subdrv_info[2]; /* sub driver information */
|
||||
};
|
||||
|
||||
typedef struct upsdrv_info_s upsdrv_info_t;
|
||||
|
||||
/* flags to define the driver development status */
|
||||
#define DRV_BROKEN 0x0001 /* dito... */
|
||||
#define DRV_EXPERIMENTAL 0x0002 /* dito... */
|
||||
#define DRV_BETA 0x0004 /* more stable and complete, but still
|
||||
* not suitable for production systems
|
||||
*/
|
||||
#define DRV_STABLE 0x0008 /* suitable for production systems, but
|
||||
* not 100 % feature complete */
|
||||
#define DRV_COMPLETE 0x0010 /* gold level: implies 100 % of the
|
||||
* protocol implemented and the full QA
|
||||
* pass */
|
||||
/* FIXME: complete with mfr support, and other interesting info */
|
||||
|
||||
/* public driver information from the driver file */
|
||||
extern upsdrv_info_t upsdrv_info;
|
||||
|
||||
#endif /* MAIN_H */
|
||||
628
drivers/masterguard.c
Normal file
628
drivers/masterguard.c
Normal file
|
|
@ -0,0 +1,628 @@
|
|||
/* masterguard.c - support for Masterguard models
|
||||
|
||||
Copyright (C) 2001 Michael Spanier <mail@michael-spanier.de>
|
||||
|
||||
masterguard.c created on 15.8.2001
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
|
||||
/********************************************************************
|
||||
*
|
||||
* Please if you edit this code convert tabs to spaces and use
|
||||
* four characters indent.
|
||||
* If you don't know what this means use the vim editor.
|
||||
*
|
||||
* Have fun
|
||||
* Michael
|
||||
*
|
||||
********************************************************************/
|
||||
#include "main.h"
|
||||
#include "serial.h"
|
||||
|
||||
#define DRIVER_NAME "MASTERGUARD UPS driver"
|
||||
#define DRIVER_VERSION "0.24"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Michael Spanier <mail@michael-spanier.de>",
|
||||
DRV_STABLE,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
#define UPSDELAY 3
|
||||
#define MAXTRIES 10
|
||||
#define UPS_PACE 100 /* 100 us between chars on write */
|
||||
|
||||
#define Q1 1
|
||||
#define Q3 2
|
||||
|
||||
#define DEBUG 1
|
||||
|
||||
int type;
|
||||
char name[31];
|
||||
char firmware[6];
|
||||
|
||||
/********************************************************************
|
||||
*
|
||||
* Helper function to split a sting into words by splitting at the
|
||||
* SPACE character.
|
||||
*
|
||||
* Adds up to maxlen characters to the char word.
|
||||
* Returns NULL on reaching the end of the string.
|
||||
*
|
||||
********************************************************************/
|
||||
static char *StringSplit( char *source, char *word, int maxlen )
|
||||
{
|
||||
int i;
|
||||
int len;
|
||||
int wc=0;
|
||||
|
||||
word[0] = '\0';
|
||||
len = strlen( source );
|
||||
for( i = 0; i < len; i++ )
|
||||
{
|
||||
if( source[i] == ' ' )
|
||||
{
|
||||
word[wc] = '\0';
|
||||
return source + i + 1;
|
||||
}
|
||||
word[wc] = source[i];
|
||||
wc++;
|
||||
}
|
||||
word[wc] = '\0';
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/********************************************************************
|
||||
*
|
||||
* Helper function to drop all whitespaces within a string.
|
||||
*
|
||||
* "word" must be large enought to hold "source", for the worst case
|
||||
* "word" has to be exacly the size of "source".
|
||||
*
|
||||
********************************************************************/
|
||||
static void StringStrip( char *source, char *word )
|
||||
{
|
||||
int wc=0;
|
||||
int i;
|
||||
int len;
|
||||
|
||||
word[0] = '\0';
|
||||
len = strlen( source );
|
||||
for( i = 0; i < len; i++ )
|
||||
{
|
||||
if( source[i] == ' ' )
|
||||
continue;
|
||||
if( source[i] == '\n' )
|
||||
continue;
|
||||
if( source[i] == '\t' )
|
||||
continue;
|
||||
word[wc] = source[i];
|
||||
wc++;
|
||||
}
|
||||
word[wc] = '\0';
|
||||
}
|
||||
|
||||
/********************************************************************
|
||||
*
|
||||
* Function parses the status flags which occure in the Q1 and Q3
|
||||
* command. Sets the INFO_STATUS value ( OL, OB, ... )
|
||||
*
|
||||
********************************************************************/
|
||||
static void parseFlags( char *flags )
|
||||
{
|
||||
status_init();
|
||||
|
||||
if( flags[0] == '1' )
|
||||
status_set("OB");
|
||||
else
|
||||
status_set("OL");
|
||||
|
||||
if( flags[1] == '1' )
|
||||
status_set("LB");
|
||||
|
||||
if( flags[2] == '1' )
|
||||
status_set("BOOST");
|
||||
|
||||
/* this has no mapping */
|
||||
#if 0
|
||||
if( flags[3] == '1' )
|
||||
setinfo( INFO_ALRM_GENERAL, "1" );
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
/* and these are... ? */
|
||||
|
||||
if( flags[5] == '1' )
|
||||
status_set("TIP");
|
||||
if( flags[6] == '1' )
|
||||
status_set("SD");
|
||||
#endif
|
||||
|
||||
status_commit();
|
||||
|
||||
if( DEBUG )
|
||||
printf( "Status is %s\n", dstate_getinfo("ups.status"));
|
||||
}
|
||||
|
||||
/********************************************************************
|
||||
*
|
||||
* Function parses the response of the query1 ( "Q1" ) command.
|
||||
* Also sets various values (IPFreq ... )
|
||||
*
|
||||
********************************************************************/
|
||||
static void query1( char *buf )
|
||||
{
|
||||
#define WORDMAXLEN 255
|
||||
char value[WORDMAXLEN];
|
||||
char word[WORDMAXLEN];
|
||||
char *newPOS;
|
||||
char *oldPOS;
|
||||
int count = 0;
|
||||
|
||||
if( DEBUG )
|
||||
printf( "Q1 Buffer is : %s\n" , buf + 1 );
|
||||
oldPOS = buf + 1;
|
||||
newPOS = oldPOS;
|
||||
|
||||
do
|
||||
{
|
||||
newPOS = StringSplit( oldPOS, word, WORDMAXLEN );
|
||||
StringStrip( word, value);
|
||||
oldPOS = newPOS;
|
||||
|
||||
if( DEBUG )
|
||||
{
|
||||
printf( "value=%s\n", value );
|
||||
fflush( stdout );
|
||||
}
|
||||
switch( count )
|
||||
{
|
||||
case 0:
|
||||
/* IP Voltage */
|
||||
dstate_setinfo("input.voltage", "%s", value );
|
||||
break;
|
||||
case 1:
|
||||
/* IP Fault Voltage */
|
||||
break;
|
||||
case 2:
|
||||
/* OP Voltage */
|
||||
dstate_setinfo("output.voltage", "%s", value);
|
||||
break;
|
||||
case 3:
|
||||
/* OP Load*/
|
||||
dstate_setinfo("ups.load", "%s", value );
|
||||
break;
|
||||
case 4:
|
||||
/* IP Frequency */
|
||||
dstate_setinfo("input.frequency", "%s", value);
|
||||
break;
|
||||
case 5:
|
||||
/* Battery Cell Voltage */
|
||||
dstate_setinfo("battery.voltage", "%s", value);
|
||||
break;
|
||||
case 6:
|
||||
/* UPS Temperature */
|
||||
dstate_setinfo("ups.temperature", "%s", value );
|
||||
break;
|
||||
case 7:
|
||||
/* Flags */
|
||||
parseFlags( value );
|
||||
break;
|
||||
default:
|
||||
/* Should never be reached */
|
||||
break;
|
||||
}
|
||||
count ++;
|
||||
oldPOS = newPOS;
|
||||
}
|
||||
while( newPOS != NULL );
|
||||
}
|
||||
|
||||
/********************************************************************
|
||||
*
|
||||
* Function parses the response of the query3 ( "Q3" ) command.
|
||||
* Also sets various values (IPFreq ... )
|
||||
*
|
||||
********************************************************************/
|
||||
static void query3( char *buf )
|
||||
{
|
||||
#define WORDMAXLEN 255
|
||||
char value[WORDMAXLEN];
|
||||
char word[WORDMAXLEN];
|
||||
char *newPOS;
|
||||
char *oldPOS;
|
||||
int count = 0;
|
||||
|
||||
if( DEBUG )
|
||||
printf( "Q3 Buffer is : %s\n" , buf+1 );
|
||||
oldPOS = buf + 1;
|
||||
newPOS = oldPOS;
|
||||
|
||||
do
|
||||
{
|
||||
newPOS = StringSplit( oldPOS, word, WORDMAXLEN );
|
||||
StringStrip( word, value);
|
||||
oldPOS = newPOS;
|
||||
|
||||
/* Shortcut */
|
||||
if( newPOS == NULL )
|
||||
break;
|
||||
|
||||
if( DEBUG )
|
||||
{
|
||||
printf( "value=%s\n", value );
|
||||
fflush( stdout );
|
||||
}
|
||||
switch( count )
|
||||
{
|
||||
case 0:
|
||||
/* UPS ID */
|
||||
break;
|
||||
case 1:
|
||||
/* Input Voltage */
|
||||
dstate_setinfo("input.voltage", "%s", value );
|
||||
break;
|
||||
case 2:
|
||||
/* Input Fault Voltage */
|
||||
break;
|
||||
case 3:
|
||||
/* Output Voltage */
|
||||
dstate_setinfo("output.voltage", "%s", value);
|
||||
break;
|
||||
case 4:
|
||||
/* Output Current */
|
||||
dstate_setinfo("output.current", "%s", value );
|
||||
break;
|
||||
case 5:
|
||||
/* Input Frequency */
|
||||
dstate_setinfo("input.frequency", "%s", value);
|
||||
break;
|
||||
case 6:
|
||||
/* Battery Cell Voltage */
|
||||
dstate_setinfo("battery.voltage", "%s", value);
|
||||
break;
|
||||
case 7:
|
||||
/* Temperature */
|
||||
dstate_setinfo("ups.temperature", "%s", value );
|
||||
break;
|
||||
case 8:
|
||||
/* Estimated Runtime */
|
||||
dstate_setinfo("battery.runtime", "%s", value);
|
||||
break;
|
||||
case 9:
|
||||
/* Charge Status */
|
||||
dstate_setinfo("battery.charge", "%s", value);
|
||||
break;
|
||||
case 10:
|
||||
/* Flags */
|
||||
parseFlags( value );
|
||||
break;
|
||||
case 11:
|
||||
/* Flags2 */
|
||||
break;
|
||||
default:
|
||||
/* This should never be reached */
|
||||
/* printf( "DEFAULT\n" ); */
|
||||
break;
|
||||
}
|
||||
count ++;
|
||||
oldPOS = newPOS;
|
||||
}
|
||||
while( newPOS != NULL );
|
||||
}
|
||||
|
||||
/********************************************************************
|
||||
*
|
||||
* Function to parse the WhoAmI response of the UPS. Also sets the
|
||||
* values of the firmware version and the UPS identification.
|
||||
*
|
||||
********************************************************************/
|
||||
static void parseWH( char *buf )
|
||||
{
|
||||
strncpy( name, buf + 16, 30 );
|
||||
name[30] = '\0';
|
||||
strncpy( firmware, buf + 4, 5 );
|
||||
firmware[5] = '\0';
|
||||
if( DEBUG )
|
||||
printf( "Name = %s, Firmware Version = %s\n", name, firmware );
|
||||
}
|
||||
|
||||
/********************************************************************
|
||||
*
|
||||
* Function to parse the old and possible broken WhoAmI response
|
||||
* and set the values for the firmware Version and the identification
|
||||
* of the UPS.
|
||||
*
|
||||
********************************************************************/
|
||||
static void parseOldWH( char *buf )
|
||||
{
|
||||
strncpy( name, buf + 4, 12 );
|
||||
name[12] = '\0';
|
||||
strncpy( firmware, buf, 4 );
|
||||
firmware[4] = '\0';
|
||||
if( DEBUG )
|
||||
printf( "Name = %s, Firmware Version = %s\n", name, firmware );
|
||||
}
|
||||
|
||||
/********************************************************************
|
||||
*
|
||||
* Function to fake a WhoAmI response of a UPS that returns NAK.
|
||||
*
|
||||
********************************************************************/
|
||||
static void fakeWH(void)
|
||||
{
|
||||
strcpy( name, "GenericUPS" );
|
||||
strcpy( firmware, "unkn" );
|
||||
if( DEBUG )
|
||||
printf( "Name = %s, Firmware Version = %s\n", name, firmware );
|
||||
}
|
||||
|
||||
static int ups_ident( void )
|
||||
{
|
||||
char buf[255];
|
||||
int ret;
|
||||
|
||||
/* Check presence of Q1 */
|
||||
ret = ser_send_pace(upsfd, UPS_PACE, "%s", "Q1\x0D" );
|
||||
ret = ser_get_line(upsfd, buf, sizeof(buf), '\r', "", 3, 0);
|
||||
ret = strlen( buf );
|
||||
if( ret != 46 )
|
||||
{
|
||||
/* No Q1 response found */
|
||||
type = 0;
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if( DEBUG )
|
||||
printf( "Found Q1\n" );
|
||||
type = Q1;
|
||||
}
|
||||
|
||||
/* Check presence of Q3 */
|
||||
ret = ser_send_pace(upsfd, UPS_PACE, "%s", "Q3\x0D" );
|
||||
ret = ser_get_line(upsfd, buf, sizeof(buf), '\r', "", 3, 0);
|
||||
ret = strlen( buf );
|
||||
if( ret == 70 )
|
||||
{
|
||||
if( DEBUG )
|
||||
printf( "Found Q3\n" );
|
||||
type = Q1 | Q3;
|
||||
}
|
||||
|
||||
/* Check presence of WH ( Who am I ) */
|
||||
ret = ser_send_pace(upsfd, UPS_PACE, "%s", "WH\x0D" );
|
||||
ret = ser_get_line(upsfd, buf, sizeof(buf), '\r', "", 3, 0);
|
||||
ret = strlen( buf );
|
||||
if( ret == 112 )
|
||||
{
|
||||
if( DEBUG )
|
||||
printf( "WH found\n" );
|
||||
parseWH( buf );
|
||||
}
|
||||
else if( ret == 53 )
|
||||
{
|
||||
if( DEBUG )
|
||||
printf( "Old (broken) WH found\n" );
|
||||
parseOldWH( buf );
|
||||
}
|
||||
else if( ret == 3 && strcmp(buf, "NAK") == 0 )
|
||||
{
|
||||
if( DEBUG )
|
||||
printf( "WH was NAKed\n" );
|
||||
fakeWH( );
|
||||
}
|
||||
else if( ret > 0 )
|
||||
{
|
||||
if( DEBUG )
|
||||
printf( "WH says <%s> with length %i\n", buf, ret );
|
||||
upslog_with_errno( LOG_INFO,
|
||||
"New WH String found. Please report to maintainer\n" );
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/********************************************************************
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
********************************************************************/
|
||||
void upsdrv_help( void )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/********************************************************************
|
||||
*
|
||||
* Function to initialize the fields of the ups driver.
|
||||
*
|
||||
********************************************************************/
|
||||
void upsdrv_initinfo(void)
|
||||
{
|
||||
dstate_setinfo("ups.mfr", "MASTERGUARD");
|
||||
dstate_setinfo("ups.model", "unknown");
|
||||
|
||||
/*
|
||||
dstate_addcmd("test.battery.stop");
|
||||
dstate_addcmd("test.battery.start");
|
||||
*/
|
||||
|
||||
if( strlen( name ) > 0 )
|
||||
dstate_setinfo("ups.model", "%s", name);
|
||||
if( strlen( firmware ) > 0 )
|
||||
dstate_setinfo("ups.firmware", "%s", firmware);
|
||||
|
||||
dstate_setinfo("driver.version.internal", "%s", DRIVER_VERSION);
|
||||
}
|
||||
|
||||
/********************************************************************
|
||||
*
|
||||
* This is the main function. It gets called if the driver wants
|
||||
* to update the ups status and the informations.
|
||||
*
|
||||
********************************************************************/
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
char buf[255];
|
||||
int ret;
|
||||
int lenRSP=0;
|
||||
|
||||
if( DEBUG )
|
||||
printf( "update info\n" );
|
||||
|
||||
/* Q3 found ? */
|
||||
if( type & Q3 )
|
||||
{
|
||||
ser_send_pace(upsfd, UPS_PACE, "%s", "Q3\x0D" );
|
||||
lenRSP = 70;
|
||||
}
|
||||
/* Q1 found ? */
|
||||
else if( type & Q1 )
|
||||
{
|
||||
ser_send_pace(upsfd, UPS_PACE, "%s", "Q1\x0D" );
|
||||
lenRSP = 46;
|
||||
}
|
||||
/* Should never be reached */
|
||||
else
|
||||
{
|
||||
fatalx(EXIT_FAILURE, "Error, no Query mode defined. Please file bug against driver.");
|
||||
}
|
||||
|
||||
sleep( UPSDELAY );
|
||||
|
||||
buf[0] = '\0';
|
||||
ret = ser_get_line(upsfd, buf, sizeof(buf), '\r', "", 3, 0);
|
||||
ret = strlen( buf );
|
||||
|
||||
if( ret != lenRSP )
|
||||
{
|
||||
if( DEBUG )
|
||||
printf( "buf = %s len = %i\n", buf, ret );
|
||||
upslog_with_errno( LOG_ERR, "Error in UPS response " );
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Parse the response from the UPS */
|
||||
if( type & Q3 )
|
||||
{
|
||||
query3( buf );
|
||||
dstate_dataok();
|
||||
return;
|
||||
}
|
||||
if( type & Q1 )
|
||||
{
|
||||
query1( buf );
|
||||
dstate_dataok();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/********************************************************************
|
||||
*
|
||||
* Called if the driver wants to shutdown the UPS.
|
||||
* ( also used by the "-k" command line switch )
|
||||
*
|
||||
* This cuts the utility from the UPS after 20 seconds and restores
|
||||
* the utility one minute _after_ the utility to the UPS has restored
|
||||
*
|
||||
********************************************************************/
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
/* ups will come up within a minute if utility is restored */
|
||||
ser_send_pace(upsfd, UPS_PACE, "%s", "S.2R0001\x0D" );
|
||||
}
|
||||
|
||||
/********************************************************************
|
||||
*
|
||||
* Populate the command line switches.
|
||||
*
|
||||
* CS: Cancel the shutdown process
|
||||
*
|
||||
********************************************************************/
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
addvar( VAR_FLAG, "CS", "Cancel Shutdown" );
|
||||
}
|
||||
|
||||
/********************************************************************
|
||||
*
|
||||
* This is the first function called by the UPS driver.
|
||||
* Detects the UPS and handles the command line args.
|
||||
*
|
||||
********************************************************************/
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
int count = 0;
|
||||
int fail = 0;
|
||||
int good = 0;
|
||||
|
||||
/* setup serial port */
|
||||
upsfd = ser_open(device_path);
|
||||
ser_set_speed(upsfd, device_path, B2400);
|
||||
|
||||
name[0] = '\0';
|
||||
firmware[0] = '\0';
|
||||
|
||||
/* probe ups type */
|
||||
do
|
||||
{
|
||||
count++;
|
||||
|
||||
if( ups_ident( ) != 1 )
|
||||
fail++;
|
||||
/* at least two good identifications */
|
||||
if( (count - fail) == 2 )
|
||||
{
|
||||
good = 1;
|
||||
break;
|
||||
}
|
||||
} while( (count<MAXTRIES) | (good) );
|
||||
|
||||
if( ! good )
|
||||
{
|
||||
fatalx(EXIT_FAILURE, "No MASTERGUARD UPS found" );
|
||||
}
|
||||
|
||||
upslogx(LOG_INFO, "MASTERGUARD UPS found\n" );
|
||||
|
||||
/* Cancel Shutdown */
|
||||
if( testvar("CS") )
|
||||
{
|
||||
ser_send_pace(upsfd, UPS_PACE, "%s", "C\x0D" );
|
||||
fatalx(EXIT_FAILURE, "Shutdown cancelled");
|
||||
}
|
||||
}
|
||||
|
||||
/********************************************************************
|
||||
*
|
||||
* VIM Preferences.
|
||||
* As you probably know vim is the best editor ever ;-)
|
||||
* http://www.vim.org
|
||||
*
|
||||
* vim:ts=4:sw=4:tw=78:et
|
||||
*
|
||||
********************************************************************/
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
ser_close(upsfd, device_path);
|
||||
}
|
||||
1036
drivers/megatec.c
Normal file
1036
drivers/megatec.c
Normal file
File diff suppressed because it is too large
Load diff
42
drivers/megatec.h
Normal file
42
drivers/megatec.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: t; -*-
|
||||
*
|
||||
* megatec.h: support for Megatec protocol based UPSes
|
||||
*
|
||||
* Copyright (C) Carlos Rodrigues <carlos.efr at mail.telepac.pt>
|
||||
*
|
||||
* megatec.c created on 4/10/2003
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
|
||||
#ifndef MEGATEC_H
|
||||
#define MEGATEC_H
|
||||
|
||||
/* FIXME (AQ): non compliant version (should be X.YZ)
|
||||
* USB and serial should also be versioned */
|
||||
|
||||
|
||||
#ifdef MEGATEC_SUBDRV
|
||||
extern upsdrv_info_t megatec_subdrv_info;
|
||||
void megatec_subdrv_makevartable(void);
|
||||
void megatec_subdrv_banner(void);
|
||||
#else
|
||||
# define megatec_subdrv_makevartable(...)
|
||||
# define megatec_subdrv_banner(...)
|
||||
#endif
|
||||
|
||||
|
||||
#endif /* MEGATEC_H */
|
||||
656
drivers/megatec_usb.c
Normal file
656
drivers/megatec_usb.c
Normal file
|
|
@ -0,0 +1,656 @@
|
|||
/* megatec_usb.c - usb communication layer for Megatec protocol based UPSes
|
||||
*
|
||||
* Copyright (C) 2006 Andrey Lelikov <nut-driver@lelik.org>
|
||||
* Copyright (C) 2007 Alexander Gordeev <lasaine@lvk.cs.msu.su>
|
||||
* Copyright (C) 2007 Jon Gough <jon.gough at eclipsesystems.com.au>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "megatec.h"
|
||||
#include "libusb.h"
|
||||
#include "serial.h"
|
||||
#include "usb-common.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define SUB_DRIVER_VERSION "0.10"
|
||||
#define SUB_DRIVER_NAME "Serial-over-USB transport layer"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t megatec_subdrv_info = {
|
||||
SUB_DRIVER_NAME,
|
||||
SUB_DRIVER_VERSION,
|
||||
"Andrey Lelikov <nut-driver@lelik.org>\n" \
|
||||
"Alexander Gordeev <lasaine@lvk.cs.msu.su>\n" \
|
||||
"Jon Gough <jon.gough@eclipsesystems.com.au>",
|
||||
DRV_STABLE,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
/*
|
||||
This is a communication driver for "USB HID" UPS-es which use proprietary
|
||||
usb-to-serial converter and speak megatec protocol. Usually these are cheap
|
||||
models and usb-to-serial converter is a huge oem hack - HID tables are bogus,
|
||||
device has no UPS reports, etc.
|
||||
This driver has a table of all known devices which has pointers to device-
|
||||
specific communication functions (namely send a string to UPS and read a string
|
||||
from it). Driver takes care of detection, opening a usb device, string
|
||||
formatting etc. So in order to add support for another usb-to-serial device one
|
||||
only needs to implement device-specific get/set functions and add an entry into
|
||||
KnownDevices table.
|
||||
|
||||
*/
|
||||
|
||||
static usb_communication_subdriver_t *usb = &usb_subdriver;
|
||||
static usb_dev_handle *udev = NULL;
|
||||
static USBDevice_t usbdevice;
|
||||
static USBDeviceMatcher_t *reopen_matcher = NULL;
|
||||
static USBDeviceMatcher_t *regex_matcher = NULL;
|
||||
|
||||
static int (*get_data)(char *buffer, int buffer_size) = NULL;
|
||||
static int (*set_data)(const char *str) = NULL;
|
||||
|
||||
/* agiler-old subdriver definition */
|
||||
static int get_data_agiler_old(char *buffer, int buffer_size);
|
||||
static int set_data_agiler_old(const char *str);
|
||||
|
||||
static void *agiler_old_subdriver(void)
|
||||
{
|
||||
get_data = &get_data_agiler_old;
|
||||
set_data = &set_data_agiler_old;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* agiler subdriver definition */
|
||||
static int get_data_agiler(char *buffer, int buffer_size);
|
||||
static int set_data_agiler(const char *str);
|
||||
|
||||
static void *agiler_subdriver(void)
|
||||
{
|
||||
get_data = &get_data_agiler;
|
||||
set_data = &set_data_agiler;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Phoenixtec Power Co subdriver definition */
|
||||
static int get_data_phoenix(char *buffer, int buffer_size);
|
||||
static int set_data_phoenix(const char *str);
|
||||
|
||||
static void *phoenix_subdriver(void)
|
||||
{
|
||||
get_data = &get_data_phoenix;
|
||||
set_data = &set_data_phoenix;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* krauler (ablerex) subdriver definition */
|
||||
static int get_data_krauler(char *buffer, int buffer_size);
|
||||
static int set_data_krauler(const char *str);
|
||||
|
||||
static void *krauler_subdriver(void)
|
||||
{
|
||||
get_data = &get_data_krauler;
|
||||
set_data = &set_data_krauler;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* list of subdrivers for manual overrides */
|
||||
static const struct {
|
||||
const char *name;
|
||||
void *(*handler)(void);
|
||||
} subdriver[] = {
|
||||
{ "agiler-old", &agiler_old_subdriver },
|
||||
{ "agiler", &agiler_subdriver },
|
||||
{ "phoenix", &phoenix_subdriver },
|
||||
{ "krauler", &krauler_subdriver },
|
||||
/* end of list */
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
/* list of all known devices */
|
||||
static usb_device_id_t megatec_usb_id[] = {
|
||||
/* Agiler UPS */
|
||||
{ USB_DEVICE(0x05b8, 0x0000), &agiler_subdriver},
|
||||
/* Krauler UP-M500VA */
|
||||
{ USB_DEVICE(0x0001, 0x0000), &krauler_subdriver},
|
||||
/* Ablerex 625L USB */
|
||||
{ USB_DEVICE(0xffff, 0x0000), &krauler_subdriver},
|
||||
/* Belkin F6C1200-UNV */
|
||||
{ USB_DEVICE(0x0665, 0x5161), &phoenix_subdriver},
|
||||
/* Mustek Powermust */
|
||||
{ USB_DEVICE(0x06da, 0x0003), &phoenix_subdriver},
|
||||
/* Unitek Alpha 1200Sx */
|
||||
{ USB_DEVICE(0x0f03, 0x0001), &phoenix_subdriver},
|
||||
/* end of list */
|
||||
{-1, -1, NULL}
|
||||
};
|
||||
|
||||
static int subdriver_match_func(USBDevice_t *d, void *privdata)
|
||||
{
|
||||
if(getval("subdriver"))
|
||||
return 1;
|
||||
|
||||
switch (is_usb_device_supported(megatec_usb_id, d->VendorID, d->ProductID))
|
||||
{
|
||||
case SUPPORTED:
|
||||
return 1;
|
||||
|
||||
case POSSIBLY_SUPPORTED:
|
||||
case NOT_SUPPORTED:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static USBDeviceMatcher_t subdriver_matcher = {
|
||||
&subdriver_match_func,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
static void usb_open_error(const char *port)
|
||||
{
|
||||
fatalx(EXIT_FAILURE,
|
||||
"No supported devices found. Please check your device availability with 'lsusb'\n"
|
||||
"and make sure you have an up-to-date version of NUT. If this does not help,\n"
|
||||
"try running the driver with at least 'vendorid' and 'subdriver' options\n"
|
||||
"specified. Please refer to the man page for details about these options\n"
|
||||
"(man 8 megatec_usb).\n"
|
||||
"Please report your results to the NUT user's mailing list\n"
|
||||
"<nut-upsuser@lists.alioth.debian.org>.\n"
|
||||
);
|
||||
}
|
||||
|
||||
/* FIXME: Fix "serial" variable (which conflicts with "serial" variable in megatec.c) */
|
||||
void megatec_subdrv_makevartable()
|
||||
{
|
||||
addvar(VAR_VALUE, "vendor", "Regular expression to match UPS Manufacturer string");
|
||||
addvar(VAR_VALUE, "product", "Regular expression to match UPS Product string");
|
||||
/* addvar(VAR_VALUE, "serial", "Regular expression to match UPS Serial number"); */
|
||||
addvar(VAR_VALUE, "vendorid", "Regular expression to match UPS Manufacturer numerical ID (4 digits hexadecimal)");
|
||||
addvar(VAR_VALUE, "productid", "Regular expression to match UPS Product numerical ID (4 digits hexadecimal)");
|
||||
addvar(VAR_VALUE, "bus", "Regular expression to match USB bus name");
|
||||
addvar(VAR_VALUE, "subdriver", "Serial-over-USB subdriver selection");
|
||||
}
|
||||
|
||||
int ser_open(const char *port)
|
||||
{
|
||||
char *regex_array[6];
|
||||
int ret;
|
||||
|
||||
char *subdrv = getval("subdriver");
|
||||
char *vid = getval("vendorid");
|
||||
char *pid = getval("productid");
|
||||
char *vend = getval("vendor");
|
||||
char *prod = getval("product");
|
||||
|
||||
/* pick up the subdriver name if set explicitly */
|
||||
if(subdrv)
|
||||
{
|
||||
int i;
|
||||
|
||||
if(!vid && !pid && !vend && !prod)
|
||||
{
|
||||
upslogx(LOG_WARNING, "It's unsafe to select a subdriver but not specify device!\n"
|
||||
"Please set some of \"vendor\", \"product\", \"vendorid\", \"productid\""
|
||||
" variables.\n");
|
||||
}
|
||||
|
||||
for (i = 0; subdriver[i].name; i++)
|
||||
{
|
||||
if (!strcasecmp(subdrv, subdriver[i].name))
|
||||
{
|
||||
(*subdriver[i].handler)();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!subdriver[i].name)
|
||||
fatalx(EXIT_FAILURE, "No subdrivers named \"%s\" found!", subdrv);
|
||||
}
|
||||
|
||||
/* FIXME: fix "serial" variable */
|
||||
/* process the UPS selection options */
|
||||
regex_array[0] = vid;
|
||||
regex_array[1] = pid;
|
||||
regex_array[2] = vend;
|
||||
regex_array[3] = prod;
|
||||
regex_array[4] = NULL; /* getval("serial"); */
|
||||
regex_array[5] = getval("bus");
|
||||
|
||||
ret = USBNewRegexMatcher(®ex_matcher, regex_array, REG_ICASE | REG_EXTENDED);
|
||||
if (ret == -1) {
|
||||
fatal_with_errno(EXIT_FAILURE, "USBNewRegexMatcher");
|
||||
} else if (ret) {
|
||||
fatalx(EXIT_FAILURE, "invalid regular expression: %s", regex_array[ret]);
|
||||
}
|
||||
/* link the matchers */
|
||||
regex_matcher->next = &subdriver_matcher;
|
||||
|
||||
ret = usb->open(&udev, &usbdevice, regex_matcher, NULL);
|
||||
if (ret < 0)
|
||||
usb_open_error(port);
|
||||
|
||||
/* create a new matcher for later reopening */
|
||||
ret = USBNewExactMatcher(&reopen_matcher, &usbdevice);
|
||||
if (ret) {
|
||||
fatal_with_errno(EXIT_FAILURE, "USBNewExactMatcher");
|
||||
}
|
||||
/* link the matchers */
|
||||
reopen_matcher->next = regex_matcher;
|
||||
|
||||
/* NOTE: This is here until ser_flush_io() is used in megatec.c */
|
||||
ser_flush_io(0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ser_set_speed(int fd, const char *port, speed_t speed)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ser_set_dtr(int fd, int state)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ser_set_rts(int fd, int state)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ser_flush_io(int fd)
|
||||
{
|
||||
char flush_buf[256];
|
||||
int i;
|
||||
|
||||
/* flush input buffers */
|
||||
for (i = 0; i < 10; i++) {
|
||||
if ((*get_data)(flush_buf, sizeof(flush_buf)) < 1)
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ser_comm_fail(const char *fmt, ...)
|
||||
{
|
||||
}
|
||||
|
||||
void ser_comm_good(void)
|
||||
{
|
||||
}
|
||||
|
||||
int ser_close(int fd, const char *port)
|
||||
{
|
||||
usb->close(udev);
|
||||
USBFreeExactMatcher(reopen_matcher);
|
||||
USBFreeRegexMatcher(regex_matcher);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!@brief Try to reconnect once.
|
||||
* @return 1 if reconnection was successful.
|
||||
*/
|
||||
static int reconnect_ups(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
upsdebugx(2, "==================================================");
|
||||
upsdebugx(2, "= device has been disconnected, try to reconnect =");
|
||||
upsdebugx(2, "==================================================");
|
||||
|
||||
usb->close(udev);
|
||||
|
||||
ret = usb->open(&udev, &usbdevice, reopen_matcher, NULL);
|
||||
if (ret < 1) {
|
||||
upslogx(LOG_INFO, "Reconnecting to UPS failed; will retry later...");
|
||||
udev = NULL;
|
||||
return 0;
|
||||
} else
|
||||
upslogx(LOG_NOTICE, "Successfully reconnected");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*!@brief Report a USB comm failure, and reconnect if necessary
|
||||
*
|
||||
* @param[in] res Result code from libusb/libhid call
|
||||
* @param[in] msg Error message to display
|
||||
*/
|
||||
void usb_comm_fail(int res, const char *msg)
|
||||
{
|
||||
switch(res) {
|
||||
case -EBUSY:
|
||||
upslogx(LOG_WARNING, "%s: Device claimed by another process", msg);
|
||||
fatalx(EXIT_FAILURE, "Terminating: EBUSY");
|
||||
|
||||
default:
|
||||
upslogx(LOG_WARNING, "%s: Device detached? (error %d: %s)", msg, res, usb_strerror());
|
||||
|
||||
if(reconnect_ups()) {
|
||||
/* upsdrv_initinfo(); */
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int ser_send_pace(int fd, unsigned long d_usec, const char *fmt, ...)
|
||||
{
|
||||
char buf[128];
|
||||
size_t len;
|
||||
va_list ap;
|
||||
int ret;
|
||||
|
||||
if ((udev == NULL) && (! reconnect_ups()))
|
||||
return -1;
|
||||
|
||||
va_start(ap, fmt);
|
||||
|
||||
len = vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
|
||||
va_end(ap);
|
||||
|
||||
if ((len < 1) || (len >= (int) sizeof(buf))) {
|
||||
upslogx(LOG_WARNING, "ser_send_pace: vsnprintf needed more than %d bytes", (int) sizeof(buf));
|
||||
buf[sizeof(buf) - 1] = 0;
|
||||
}
|
||||
|
||||
ret = (*set_data)(buf);
|
||||
if(ret < 0) {
|
||||
usb_comm_fail(ret, "ser_send_pace");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ser_get_line(int fd, void *buf, size_t buflen, char endchar, const char *ignset, long d_sec, long d_usec)
|
||||
{
|
||||
int len;
|
||||
char *src, *dst, c;
|
||||
|
||||
if ((udev == NULL) && (! reconnect_ups()))
|
||||
return -1;
|
||||
|
||||
len = (*get_data)((char *)buf, buflen);
|
||||
if (len < 0) {
|
||||
usb_comm_fail(len, "ser_get_line");
|
||||
return len;
|
||||
}
|
||||
|
||||
dst = (char *)buf;
|
||||
|
||||
for (src = (char *)buf; src != ((char *)buf + len); src++) {
|
||||
c = *src;
|
||||
|
||||
if (c == endchar)
|
||||
break;
|
||||
|
||||
if ((c == 0) || ((ignset != NULL) && (strchr(ignset, c) != NULL)))
|
||||
continue;
|
||||
|
||||
*(dst++) = c;
|
||||
}
|
||||
|
||||
/* terminate string if we have space */
|
||||
if (dst != ((char *)buf + len))
|
||||
*dst = 0;
|
||||
|
||||
return (dst - (char *)buf);
|
||||
}
|
||||
|
||||
/************** minidrivers go after this point **************************/
|
||||
|
||||
/*
|
||||
agiler-old subdriver
|
||||
*/
|
||||
/* Protocol was reverse-engineered from Windows driver
|
||||
HID tables are completely bogus
|
||||
Data is transferred out as one 8-byte packet with report ID 0
|
||||
Data comes in as 6 8-byte reports per line , padded with zeroes
|
||||
All constants are hardcoded in windows driver
|
||||
*/
|
||||
|
||||
#define AGILER_REPORT_SIZE 8
|
||||
#define AGILER_REPORT_COUNT 6
|
||||
#define AGILER_TIMEOUT 5000
|
||||
|
||||
static int set_data_agiler_old(const char *str)
|
||||
{
|
||||
unsigned char report_buf[AGILER_REPORT_SIZE];
|
||||
|
||||
if (strlen(str) > AGILER_REPORT_SIZE) {
|
||||
upslogx(LOG_ERR, "set_data_agiler: output string too large");
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(report_buf, 0, sizeof(report_buf));
|
||||
memcpy(report_buf, str, strlen(str));
|
||||
|
||||
return usb->set_report(udev, 0, report_buf, sizeof(report_buf));
|
||||
}
|
||||
|
||||
static int get_data_agiler_old(char *buffer, int buffer_size)
|
||||
{
|
||||
int i, len;
|
||||
char buf[AGILER_REPORT_SIZE * AGILER_REPORT_COUNT + 1];
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
|
||||
for (i = 0; i < AGILER_REPORT_COUNT; i++) {
|
||||
len = usb->get_interrupt(udev, (unsigned char *) buf + i * AGILER_REPORT_SIZE, AGILER_REPORT_SIZE, AGILER_TIMEOUT);
|
||||
if (len != AGILER_REPORT_SIZE) {
|
||||
if (len < 0)
|
||||
len = 0;
|
||||
buf[i * AGILER_REPORT_SIZE + len] = 0;
|
||||
upsdebug_hex(5, "get_data_agiler: raw dump", buf, i * AGILER_REPORT_SIZE + len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
len = strlen(buf);
|
||||
|
||||
if (len > buffer_size) {
|
||||
upslogx(LOG_ERR, "get_data_agiler: input buffer too small");
|
||||
len = buffer_size;
|
||||
}
|
||||
|
||||
memcpy(buffer, buf, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Agiler serial-to-usb device.
|
||||
*/
|
||||
|
||||
static int set_data_agiler(const char *str)
|
||||
{
|
||||
return usb->set_report(udev, 0, (unsigned char *)str, strlen(str));
|
||||
}
|
||||
|
||||
static int get_data_agiler(char *buffer, int buffer_size)
|
||||
{
|
||||
return usb->get_interrupt(udev, (unsigned char *)buffer, buffer_size, 1000);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Phoenixtec Power Co serial-to-usb device.
|
||||
*/
|
||||
static char phoenix_buffer[32];
|
||||
|
||||
static int set_data_phoenix(const char *str)
|
||||
{
|
||||
unsigned int count;
|
||||
|
||||
memset(phoenix_buffer, '\0', sizeof(phoenix_buffer));
|
||||
|
||||
snprintf(phoenix_buffer, sizeof(phoenix_buffer), "%s", str);
|
||||
|
||||
if (!strcmp(phoenix_buffer, "I\r") || !strcmp(phoenix_buffer, "C\r")) {
|
||||
/* Ignore these, since they seem to lock up the connection */
|
||||
return strlen(phoenix_buffer);
|
||||
}
|
||||
|
||||
for (count = 0; count < strlen(phoenix_buffer); count += 8) {
|
||||
|
||||
if (usb->set_report(udev, 0, (unsigned char *)(phoenix_buffer + count), 8) < 1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return strlen(phoenix_buffer);
|
||||
}
|
||||
|
||||
static int get_data_phoenix(char *buffer, int buffer_size)
|
||||
{
|
||||
int count;
|
||||
|
||||
memset(buffer, '\0', buffer_size);
|
||||
|
||||
if (!strcmp(phoenix_buffer, "I\r") || !strcmp(phoenix_buffer, "C\r")) {
|
||||
/* Echo back unsupported commands */
|
||||
snprintf(buffer, buffer_size, "%s", phoenix_buffer);
|
||||
return strlen(buffer);
|
||||
}
|
||||
|
||||
for (count = 8; count <= buffer_size; count += 8) {
|
||||
|
||||
/* Read data in 8-byte chunks, break on a timeout */
|
||||
if (usb->get_interrupt(udev, (unsigned char *)&buffer[count-8], 8, 1000) < 0) {
|
||||
return count-8;
|
||||
}
|
||||
|
||||
upsdebugx(3, "get_data_phoenix: got so far [%s]", buffer);
|
||||
upsdebug_hex(4, "get_data_phoenix", (unsigned char *)buffer, count);
|
||||
}
|
||||
|
||||
upsdebugx(3, "get_data_phoenix: buffer too small");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Krauler serial-over-usb device.
|
||||
|
||||
Protocol was reverse-engineered using Windows driver.
|
||||
*/
|
||||
|
||||
#define KRAULER_MAX_ATTEMPTS_Q1 4
|
||||
#define KRAULER_MAX_ATTEMPTS_F 31
|
||||
#define KRAULER_MAX_ATTEMPTS_I 15
|
||||
|
||||
typedef struct {
|
||||
char *str; /* Megatec command */
|
||||
int index; /* Krauler string index for this command */
|
||||
char prefix; /* character to replace the first byte in reply */
|
||||
int retry; /* number of retries (1 is typically for instant commands) */
|
||||
} krauler_command_t;
|
||||
|
||||
static krauler_command_t krauler_command_lst[] = {
|
||||
{ "T\r", 0x04, '\0', 1 },
|
||||
{ "TL\r", 0x05, '\0', 1 },
|
||||
{ "Q\r", 0x07, '\0', 1 },
|
||||
{ "C\r", 0x0b, '\0', 1 },
|
||||
{ "CT\r", 0x0b, '\0', 1 },
|
||||
{ "Q1\r", 0x03, '(', KRAULER_MAX_ATTEMPTS_Q1 },
|
||||
{ "I\r", 0x0c, '#', KRAULER_MAX_ATTEMPTS_I },
|
||||
{ "F\r", 0x0d, '#', KRAULER_MAX_ATTEMPTS_F },
|
||||
{ NULL, 0, '\0', 0 }
|
||||
};
|
||||
|
||||
/*
|
||||
Still not implemented:
|
||||
0x6 T<n> (don't know how to pass the parameter)
|
||||
0x68 and 0x69 both cause shutdown after an undefined interval
|
||||
*/
|
||||
|
||||
|
||||
/* an intermediate buffer for 1 command's output */
|
||||
static char krauler_line_buf[255];
|
||||
static char krauler_line_buf_len = 0;
|
||||
|
||||
static int set_data_krauler(const char *str)
|
||||
{
|
||||
krauler_command_t *command;
|
||||
int retval = strlen(str);
|
||||
|
||||
for (command = krauler_command_lst; command->str != NULL; command++) {
|
||||
int retry;
|
||||
|
||||
if (strcmp(str, command->str)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
upsdebugx(3, "set_data_krauler: index [%02x]", command->index);
|
||||
|
||||
krauler_line_buf_len = 0;
|
||||
for (retry = 0; retry < command->retry; retry++) {
|
||||
int res;
|
||||
|
||||
res = usb->get_string(udev, command->index, krauler_line_buf, sizeof(krauler_line_buf));
|
||||
if (res < 1) {
|
||||
/* TODO: handle_error(res) */
|
||||
upsdebugx(2, "set_data_krauler: connection failure");
|
||||
return res;
|
||||
}
|
||||
|
||||
/* "UPS No Ack" has a special meaning */
|
||||
if (!strcmp(krauler_line_buf, "UPS No Ack")) {
|
||||
upsdebugx(4, "set_data_krauler: retry [%s]", krauler_line_buf);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Replace the first byte of what we received with the correct one */
|
||||
if(command->prefix)
|
||||
krauler_line_buf[0] = command->prefix;
|
||||
|
||||
krauler_line_buf_len = res;
|
||||
return retval;
|
||||
}
|
||||
|
||||
if(command->retry > 1 && retry == command->retry)
|
||||
upsdebugx(2, "set_data_krauler: too many attempts, the UPS is probably switched off!");
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
upsdebugx(4, "set_data_krauler: unknown command [%s]", str);
|
||||
|
||||
/* echo the unknown command back */
|
||||
strcpy(krauler_line_buf, str);
|
||||
krauler_line_buf_len = retval;
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int get_data_krauler(char *buffer, int buffer_size)
|
||||
{
|
||||
int retrieved = (buffer_size < krauler_line_buf_len) ? buffer_size : krauler_line_buf_len;
|
||||
int left = krauler_line_buf_len - retrieved;
|
||||
|
||||
memcpy(buffer, krauler_line_buf, retrieved);
|
||||
memmove(krauler_line_buf, krauler_line_buf + retrieved, left);
|
||||
krauler_line_buf_len = left;
|
||||
|
||||
return retrieved;
|
||||
}
|
||||
1053
drivers/metasys.c
Normal file
1053
drivers/metasys.c
Normal file
File diff suppressed because it is too large
Load diff
1018
drivers/mge-hid.c
Normal file
1018
drivers/mge-hid.c
Normal file
File diff suppressed because it is too large
Load diff
32
drivers/mge-hid.h
Normal file
32
drivers/mge-hid.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/* mge-hid.h - data to monitor MGE UPS SYSTEMS HID (USB and serial) devices
|
||||
*
|
||||
* Copyright (C) 2003 - 2005
|
||||
* Arnaud Quette <arnaud.quette@mgeups.fr>
|
||||
*
|
||||
* Sponsored by MGE UPS SYSTEMS <http://www.mgeups.com>
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MGE_HID_H
|
||||
#define MGE_HID_H
|
||||
|
||||
#include "usbhid-ups.h"
|
||||
|
||||
extern subdriver_t mge_subdriver;
|
||||
|
||||
#endif /* MGE_HID_H */
|
||||
159
drivers/mge-mib.c
Normal file
159
drivers/mge-mib.c
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
/* mge-mib.c - data to monitor MGE UPS SYSTEMS SNMP devices with NUT
|
||||
*
|
||||
* Copyright (C) 2002-2003
|
||||
* Arnaud Quette <http://arnaud.quette.free.fr/contact.html>
|
||||
* J.W. Hoogervorst <jeroen@hoogervorst.net>
|
||||
*
|
||||
* Sponsored by MGE UPS SYSTEMS <http://www.mgeups.com>
|
||||
* and MGE Office Protection Systems <http://www.mgeops.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "mge-mib.h"
|
||||
|
||||
#define MGE_MIB_VERSION "0.4"
|
||||
|
||||
/* SNMP OIDs set */
|
||||
#define MGE_BASE_OID ".1.3.6.1.4.1.705.1"
|
||||
#define MGE_OID_MODEL_NAME MGE_BASE_OID ".1.1.0"
|
||||
|
||||
static info_lkp_t mge_lowbatt_info[] = {
|
||||
{ 1, "LB" },
|
||||
{ 2, "" },
|
||||
{ 0, "NULL" }
|
||||
};
|
||||
|
||||
static info_lkp_t mge_onbatt_info[] = {
|
||||
{ 1, "OB" },
|
||||
{ 2, "OL" },
|
||||
{ 0, "NULL" }
|
||||
};
|
||||
|
||||
static info_lkp_t mge_bypass_info[] = {
|
||||
{ 1, "BYPASS" },
|
||||
{ 2, "" },
|
||||
{ 0, "NULL" }
|
||||
};
|
||||
|
||||
static info_lkp_t mge_boost_info[] = {
|
||||
{ 1, "BOOST" },
|
||||
{ 2, "" },
|
||||
{ 0, "NULL" }
|
||||
};
|
||||
|
||||
static info_lkp_t mge_trim_info[] = {
|
||||
{ 1, "TRIM" },
|
||||
{ 2, "" },
|
||||
{ 0, "NULL" }
|
||||
};
|
||||
|
||||
static info_lkp_t mge_overload_info[] = {
|
||||
{ 1, "OVER" },
|
||||
{ 2, "" },
|
||||
{ 0, "NULL" }
|
||||
};
|
||||
|
||||
#define MGE_NOTHING_VALUE 1
|
||||
#define MGE_START_VALUE 2
|
||||
#define MGE_STOP_VALUE 3
|
||||
|
||||
/* TODO: PowerShare (per plug .1, .2, .3) and deals with delays */
|
||||
|
||||
/* Snmp2NUT lookup table */
|
||||
static snmp_info_t mge_mib[] = {
|
||||
|
||||
/* UPS page */
|
||||
{ "ups.mfr", ST_FLAG_STRING, SU_INFOSIZE, NULL, "MGE UPS SYSTEMS", SU_FLAG_STATIC | SU_FLAG_ABSENT | SU_FLAG_OK, NULL },
|
||||
{ "ups.model", ST_FLAG_STRING, SU_INFOSIZE, MGE_BASE_OID ".1.1.0", "Generic SNMP UPS", SU_FLAG_STATIC | SU_FLAG_OK, NULL },
|
||||
{ "ups.serial", ST_FLAG_STRING, SU_INFOSIZE, MGE_BASE_OID ".1.7.0", "", SU_FLAG_STATIC | SU_FLAG_OK, NULL },
|
||||
{ "ups.firmware.aux", ST_FLAG_STRING, SU_INFOSIZE, MGE_BASE_OID ".12.12.0", "", SU_FLAG_STATIC | SU_FLAG_OK, NULL },
|
||||
{ "ups.status", ST_FLAG_STRING, SU_INFOSIZE, MGE_BASE_OID ".5.14.0", "", SU_FLAG_OK | SU_STATUS_BATT, mge_lowbatt_info },
|
||||
{ "ups.status", ST_FLAG_STRING, SU_INFOSIZE, MGE_BASE_OID ".7.3.0", "", SU_FLAG_OK | SU_STATUS_BATT, mge_onbatt_info },
|
||||
{ "ups.status", ST_FLAG_STRING, SU_INFOSIZE, MGE_BASE_OID ".7.4.0", "", SU_FLAG_OK | SU_STATUS_BATT, mge_bypass_info },
|
||||
{ "ups.status", ST_FLAG_STRING, SU_INFOSIZE, MGE_BASE_OID ".7.8.0", "", SU_FLAG_OK | SU_STATUS_BATT, mge_boost_info },
|
||||
{ "ups.status", ST_FLAG_STRING, SU_INFOSIZE, MGE_BASE_OID ".7.10.0", "", SU_FLAG_OK | SU_STATUS_BATT, mge_overload_info },
|
||||
{ "ups.status", ST_FLAG_STRING, SU_INFOSIZE, MGE_BASE_OID ".7.12.0", "", SU_FLAG_OK | SU_STATUS_BATT, mge_trim_info },
|
||||
{ "ups.load", 0, 1, MGE_BASE_OID ".7.2.1.4.1", "", SU_OUTPUT_1, NULL },
|
||||
{ "ups.L1.load", 0, 1, MGE_BASE_OID ".7.2.1.4.1", "", SU_OUTPUT_3, NULL },
|
||||
{ "ups.L2.load", 0, 1, MGE_BASE_OID ".7.2.1.4.2", "", SU_OUTPUT_3, NULL },
|
||||
{ "ups.L3.load", 0, 1, MGE_BASE_OID ".7.2.1.4.3", "", SU_OUTPUT_3, NULL },
|
||||
/* { "ups.delay.shutdown", ST_FLAG_STRING | ST_FLAG_RW, 3, MGE_OID_GRACEDELAY, "", SU_FLAG_OK, NULL }, */
|
||||
|
||||
/* Input page */
|
||||
{ "input.phases", 0, 1.0, MGE_BASE_OID ".6.1.0", "", SU_FLAG_SETINT, NULL, &input_phases },
|
||||
{ "input.voltage", 0, 0.1, MGE_BASE_OID ".6.2.1.2.1", "", SU_INPUT_1, NULL },
|
||||
{ "input.L1-N.voltage", 0, 0.1, MGE_BASE_OID ".6.2.1.2.1", "", SU_INPUT_3, NULL },
|
||||
{ "input.L2-N.voltage", 0, 0.1, MGE_BASE_OID ".6.2.1.2.2", "", SU_INPUT_3, NULL },
|
||||
{ "input.L3-N.voltage", 0, 0.1, MGE_BASE_OID ".6.2.1.2.3", "", SU_INPUT_3, NULL },
|
||||
{ "input.frequency", 0, 0.1, MGE_BASE_OID ".6.2.1.3.1", "", SU_INPUT_1, NULL },
|
||||
{ "input.L1.frequency", 0, 0.1, MGE_BASE_OID ".6.2.1.3.1", "", SU_INPUT_3, NULL },
|
||||
{ "input.L2.frequency", 0, 0.1, MGE_BASE_OID ".6.2.1.3.2", "", SU_INPUT_3, NULL },
|
||||
{ "input.L3.frequency", 0, 0.1, MGE_BASE_OID ".6.2.1.3.3", "", SU_INPUT_3, NULL },
|
||||
{ "input.voltage.minimum", 0, 0.1, MGE_BASE_OID ".6.2.1.4.1", "", SU_INPUT_1, NULL },
|
||||
{ "input.L1-N.voltage.minimum", 0, 0.1, MGE_BASE_OID ".6.2.1.4.1", "", SU_INPUT_3, NULL },
|
||||
{ "input.L2-N.voltage.minimum", 0, 0.1, MGE_BASE_OID ".6.2.1.4.2", "", SU_INPUT_3, NULL },
|
||||
{ "input.L3-N.voltage.minimum", 0, 0.1, MGE_BASE_OID ".6.2.1.4.3", "", SU_INPUT_3, NULL },
|
||||
{ "input.voltage.maximum", 0, 0.1, MGE_BASE_OID ".6.2.1.5.1", "", SU_INPUT_1, NULL },
|
||||
{ "input.L1-N.voltage.maximum", 0, 0.1, MGE_BASE_OID ".6.2.1.5.1", "", SU_INPUT_3, NULL },
|
||||
{ "input.L2-N.voltage.maximum", 0, 0.1, MGE_BASE_OID ".6.2.1.5.2", "", SU_INPUT_3, NULL },
|
||||
{ "input.L3-N.voltage.maximum", 0, 0.1, MGE_BASE_OID ".6.2.1.5.3", "", SU_INPUT_3, NULL },
|
||||
{ "input.current", 0, 0.1, MGE_BASE_OID ".6.2.1.6.1", "", SU_INPUT_1, NULL },
|
||||
{ "input.L1.current", 0, 0.1, MGE_BASE_OID ".6.2.1.6.1", "", SU_INPUT_3, NULL },
|
||||
{ "input.L2.current", 0, 0.1, MGE_BASE_OID ".6.2.1.6.2", "", SU_INPUT_3, NULL },
|
||||
{ "input.L3.current", 0, 0.1, MGE_BASE_OID ".6.2.1.6.3", "", SU_INPUT_3, NULL },
|
||||
|
||||
/* Output page */
|
||||
{ "output.phases", 0, 1.0, MGE_BASE_OID ".7.1.0", "", SU_FLAG_SETINT, NULL, &output_phases },
|
||||
{ "output.voltage", 0, 0.1, MGE_BASE_OID ".7.2.1.2.1", "", SU_OUTPUT_1, NULL },
|
||||
{ "output.L1-N.voltage", 0, 0.1, MGE_BASE_OID ".7.2.1.2.1", "", SU_OUTPUT_3, NULL },
|
||||
{ "output.L2-N.voltage", 0, 0.1, MGE_BASE_OID ".7.2.1.2.2", "", SU_OUTPUT_3, NULL },
|
||||
{ "output.L3-N.voltage", 0, 0.1, MGE_BASE_OID ".7.2.1.2.3", "", SU_OUTPUT_3, NULL },
|
||||
{ "output.frequency", 0, 0.1, MGE_BASE_OID ".7.2.1.3.1", "", SU_OUTPUT_1, NULL },
|
||||
{ "output.L1.frequency", 0, 0.1, MGE_BASE_OID ".7.2.1.3.1", "", SU_OUTPUT_3, NULL },
|
||||
{ "output.L2.frequency", 0, 0.1, MGE_BASE_OID ".7.2.1.3.2", "", SU_OUTPUT_3, NULL },
|
||||
{ "output.L3.frequency", 0, 0.1, MGE_BASE_OID ".7.2.1.3.3", "", SU_OUTPUT_3, NULL },
|
||||
{ "output.current", 0, 0.1, MGE_BASE_OID ".7.2.1.5.1", "", SU_OUTPUT_1, NULL },
|
||||
{ "output.L1.current", 0, 0.1, MGE_BASE_OID ".7.2.1.5.1", "", SU_OUTPUT_3, NULL },
|
||||
{ "output.L2.current", 0, 0.1, MGE_BASE_OID ".7.2.1.5.2", "", SU_OUTPUT_3, NULL },
|
||||
{ "output.L3.current", 0, 0.1, MGE_BASE_OID ".7.2.1.5.3", "", SU_OUTPUT_3, NULL },
|
||||
|
||||
/* Battery page */
|
||||
{ "battery.charge", 0, 1, MGE_BASE_OID ".5.2.0", "", SU_FLAG_OK, NULL },
|
||||
{ "battery.runtime", 0, 1, MGE_BASE_OID ".5.1.0", "", SU_FLAG_OK, NULL },
|
||||
{ "battery.charge.low", ST_FLAG_STRING | ST_FLAG_RW, 2, MGE_BASE_OID ".4.8.0", "", SU_TYPE_INT | SU_FLAG_OK, NULL },
|
||||
{ "battery.voltage", 0, 0.1, MGE_BASE_OID ".5.5.0", "", SU_FLAG_OK, NULL },
|
||||
|
||||
/* Ambient page: Environment Sensor (ref 66 846) */
|
||||
{ "ambient.temperature", 0, 0.1, MGE_BASE_OID ".8.1.0", "", SU_TYPE_INT | SU_FLAG_OK, NULL },
|
||||
{ "ambient.humidity", 0, 0.1, MGE_BASE_OID ".8.2.0", "", SU_TYPE_INT | SU_FLAG_OK, NULL },
|
||||
|
||||
/* Outlet page */
|
||||
{ "outlet.id", 0, 1, NULL, "0", SU_FLAG_STATIC | SU_FLAG_ABSENT | SU_FLAG_OK, NULL },
|
||||
{ "outlet.desc", ST_FLAG_RW | ST_FLAG_STRING, 20, NULL, "Main Outlet", SU_FLAG_STATIC | SU_FLAG_ABSENT | SU_FLAG_OK, NULL },
|
||||
|
||||
/* instant commands. */
|
||||
{ "test.battery.start", 0, MGE_START_VALUE, MGE_BASE_OID ".10.4.0", "", SU_TYPE_CMD | SU_FLAG_OK, NULL },
|
||||
/* { "load.off", 0, MGE_START_VALUE, MGE_BASE_OID ".9.1.1.6.1", "", SU_TYPE_CMD | SU_FLAG_OK, NULL }, */
|
||||
/* { "load.on", 0, MGE_START_VALUE, MGE_BASE_OID ".9.1.1.3.1", "", SU_TYPE_CMD | SU_FLAG_OK, NULL }, */
|
||||
/* { "shutdown.return", 0, MGE_START_VALUE, MGE_BASE_OID ".9.1.1.9.1", "", SU_TYPE_CMD | SU_FLAG_OK, NULL }, */
|
||||
|
||||
/* end of structure. */
|
||||
{ NULL, 0, 0, NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
mib2nut_info_t mge = { "mge", MGE_MIB_VERSION, "", MGE_OID_MODEL_NAME, mge_mib };
|
||||
9
drivers/mge-mib.h
Normal file
9
drivers/mge-mib.h
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#ifndef MGE_MIB_H
|
||||
#define MGE_MIB_H
|
||||
|
||||
#include "main.h"
|
||||
#include "snmp-ups.h"
|
||||
|
||||
extern mib2nut_info_t mge;
|
||||
|
||||
#endif /* MGE_MIB_H */
|
||||
1455
drivers/mge-shut.c
Normal file
1455
drivers/mge-shut.c
Normal file
File diff suppressed because it is too large
Load diff
523
drivers/mge-shut.h
Normal file
523
drivers/mge-shut.h
Normal file
|
|
@ -0,0 +1,523 @@
|
|||
/* mge-shut.h - monitor MGE UPS for NUT with SHUT protocol
|
||||
*
|
||||
* Copyright (C) 2002 - 2005
|
||||
* Arnaud Quette <arnaud.quette@free.fr> & <arnaud.quette@mgeups.com>
|
||||
* Philippe Marzouk <philm@users.sourceforge.net>
|
||||
*
|
||||
* Sponsored by MGE UPS SYSTEMS <http://opensource.mgeups.com/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "hidparser.h"
|
||||
#include "hidtypes.h"
|
||||
|
||||
#define DEFAULT_TIMEOUT 3000
|
||||
#define MAX_STRING 64
|
||||
|
||||
#define DEFAULT_LOWBATT 30 /* low battery level, in % */
|
||||
#define DEFAULT_ONDELAY 3 /* delay between return of utility power
|
||||
and powering up of load, in 10 seconds units */
|
||||
#define DEFAULT_OFFDELAY 20 /* delay befor power off, in seconds */
|
||||
#define OFF_NOTIFICATION 1 /* notification off */
|
||||
|
||||
#define LIGHT_NOTIFICATION 2 /* light notification */
|
||||
#define COMPLETE_NOTIFICATION 3 /* complete notification for UPSs which do
|
||||
* not support disabling it like some early
|
||||
* Ellipse models */
|
||||
#define DEFAULT_NOTIFICATION COMPLETE_NOTIFICATION
|
||||
|
||||
/* HID definitions */
|
||||
|
||||
#define HID_REPORT_TYPE_INPUT 0x01
|
||||
#define HID_REPORT_TYPE_OUTPUT 0x02
|
||||
#define HID_REPORT_TYPE_FEATURE 0x03
|
||||
|
||||
#define REQUEST_TYPE_USB 0x80
|
||||
#define REQUEST_TYPE_HID 0x81
|
||||
#define REQUEST_TYPE_GET_REPORT 0xa1
|
||||
#define REQUEST_TYPE_SET_REPORT 0x21
|
||||
|
||||
#define DEVICE_DESCRIPTOR 0x0001
|
||||
#define CONFIG_DESCRIPTOR 0x0002
|
||||
#define STRING_DESCRIPTOR 0x0003
|
||||
#define INTERFACE_DESCRIPTOR 0x0004
|
||||
#define ENDPOINT_DESCRIPTOR 0x0005
|
||||
#define HID_DESCRIPTOR 0x0021
|
||||
#define REPORT_DESCRIPTOR 0x0022
|
||||
|
||||
#define MAX_REPORT_SIZE 0x1800
|
||||
|
||||
/* SHUT definitions - From Simplified SHUT spec */
|
||||
|
||||
#define SHUT_TYPE_REQUEST 0x01
|
||||
#define SHUT_TYPE_RESPONSE 0x04
|
||||
#define SHUT_TYPE_NOTIFY 0x05
|
||||
#define SHUT_OK 0x06
|
||||
#define SHUT_NOK 0x15
|
||||
#define SHUT_SYNC 0x16 /* complete notifications - not yet managed
|
||||
but needed for some early Ellipse models */
|
||||
#define SHUT_SYNC_LIGHT 0x17 /* partial notifications */
|
||||
#define SHUT_SYNC_OFF 0x18 /* disable notifications - only do polling */
|
||||
#define SHUT_PKT_LAST 0x80
|
||||
|
||||
/* From SHUT specifications */
|
||||
|
||||
typedef struct hid_packet {
|
||||
unsigned char bmRequestType;
|
||||
unsigned char bRequest;
|
||||
unsigned short wValue;
|
||||
unsigned short wIndex;
|
||||
unsigned short wLength;
|
||||
/* unsigned char padding[8]; for use with shut_set_report */
|
||||
} hid_packet_t;
|
||||
|
||||
typedef union hid_data_u {
|
||||
hid_packet_t hid_pkt;
|
||||
unsigned char raw_pkt[8]; /* max report lengh, was 8 */
|
||||
} hid_data_t;
|
||||
|
||||
typedef struct shut_packet {
|
||||
unsigned char bType;
|
||||
unsigned char bLength;
|
||||
hid_data_t data;
|
||||
unsigned char bChecksum;
|
||||
} shut_packet_t;
|
||||
|
||||
typedef union shut_data_u {
|
||||
shut_packet_t shut_pkt;
|
||||
unsigned char raw_pkt[11];
|
||||
} shut_data_t;
|
||||
|
||||
/* From USB/HID specifications */
|
||||
|
||||
typedef struct hid_descriptor {
|
||||
unsigned char bLength;
|
||||
unsigned char bDescriptorType;
|
||||
unsigned short bcdHID;
|
||||
unsigned char bCountryCode;
|
||||
unsigned char bNumDescriptors;
|
||||
unsigned char bReportDescriptorType;
|
||||
unsigned short wDescriptorLength;
|
||||
} hid_descriptor_t;
|
||||
|
||||
typedef union hid_desc_data_u {
|
||||
hid_descriptor_t hid_desc;
|
||||
unsigned char raw_desc[9]; /* max report lengh, aws 9 */
|
||||
} hid_desc_data_t;
|
||||
|
||||
typedef struct device_descriptor {
|
||||
unsigned char bLength;
|
||||
unsigned char bDescriptorType;
|
||||
unsigned short bcdUSB;
|
||||
unsigned char bDeviceClass;
|
||||
unsigned char bDeviceSubClass;
|
||||
unsigned char bDeviceProtocol;
|
||||
unsigned char bMaxPacketSize0;
|
||||
unsigned short idVendor;
|
||||
unsigned short idProduct;
|
||||
unsigned short bcdDevice;
|
||||
unsigned char iManufacturer;
|
||||
unsigned char iProduct;
|
||||
unsigned char iSerialNumber;
|
||||
unsigned char bNumConfigurations;
|
||||
} device_descriptor_t;
|
||||
|
||||
typedef union device_desc_data_u {
|
||||
device_descriptor_t dev_desc;
|
||||
unsigned char raw_desc[18];
|
||||
} device_desc_data_t;
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* Explicit Booleans */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
#define SHUT_FLAG_OK (1 << 0) /* show element to upsd. */
|
||||
#define SHUT_FLAG_STATIC (1 << 1) /* retrieve info only once. */
|
||||
#define SHUT_FLAG_ABSENT (1 << 2) /* data is absent in the device,
|
||||
use default value. */
|
||||
#define SHUT_FLAG_STALE (1 << 3) /* data stale, don't try too often. */
|
||||
#define SHUT_FLAG_DELAY (1 << 4) /* delay type value: formated differently. */
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* Model Name formating entries */
|
||||
/* --------------------------------------------------------------- */
|
||||
typedef struct {
|
||||
char *iProduct;
|
||||
char *iModel;
|
||||
char *finalname;
|
||||
} models_name_t;
|
||||
|
||||
models_name_t models_names [] =
|
||||
{
|
||||
/* Ellipse models */
|
||||
{ "ELLIPSE", "300", "ellipse 300" },
|
||||
{ "ELLIPSE", "500", "ellipse 500" },
|
||||
{ "ELLIPSE", "650", "ellipse 650" },
|
||||
{ "ELLIPSE", "800", "ellipse 800" },
|
||||
{ "ELLIPSE", "1200", "ellipse 1200" },
|
||||
/* Ellipse Premium models */
|
||||
{ "ellipse", "PR500", "ellipse premium 500" },
|
||||
{ "ellipse", "PR650", "ellipse premium 650" },
|
||||
{ "ellipse", "PR800", "ellipse premium 800" },
|
||||
{ "ellipse", "PR1200", "ellipse premium 1200" },
|
||||
/* Ellipse "Pro" */
|
||||
{ "ELLIPSE", "600", "Ellipse 600" },
|
||||
{ "ELLIPSE", "750", "Ellipse 750" },
|
||||
{ "ELLIPSE", "1000", "Ellipse 1000" },
|
||||
{ "ELLIPSE", "1500", "Ellipse 1500" },
|
||||
/* Ellipse "MAX" */
|
||||
{ "Ellipse MAX", "600", "Ellipse MAX 600" },
|
||||
{ "Ellipse MAX", "850", "Ellipse MAX 850" },
|
||||
{ "Ellipse MAX", "1100", "Ellipse MAX 1100" },
|
||||
{ "Ellipse MAX", "1500", "Ellipse MAX 1500" },
|
||||
/* Protection Center */
|
||||
{ "PROTECTIONCENTER", "420", "Protection Center 420" },
|
||||
{ "PROTECTIONCENTER", "500", "Protection Center 500" },
|
||||
{ "PROTECTIONCENTER", "675", "Protection Center 675" },
|
||||
/* Pulsar Evolution models */
|
||||
{ "Evolution", "500", "Pulsar Evolution 500" },
|
||||
{ "Evolution", "800", "Pulsar Evolution 800" },
|
||||
{ "Evolution", "1100", "Pulsar Evolution 1100" },
|
||||
{ "Evolution", "1500", "Pulsar Evolution 1500" },
|
||||
{ "Evolution", "2200", "Pulsar Evolution 2200" },
|
||||
{ "Evolution", "3000", "Pulsar Evolution 3000" },
|
||||
{ "Evolution", "3000XL", "Pulsar Evolution 3000 XL" },
|
||||
/* Newer Evolution models */
|
||||
{ "Evolution", "650", "Evolution 650" },
|
||||
{ "Evolution", "850", "Evolution 850" },
|
||||
{ "Evolution", "1150", "Evolution 1150" },
|
||||
{ "Evolution", "S 1250", "Evolution S 1250" },
|
||||
{ "Evolution", "1550", "Evolution 1550" },
|
||||
{ "Evolution", "S 1750", "Evolution S 1750" },
|
||||
{ "Evolution", "2000", "Evolution 2000" },
|
||||
{ "Evolution", "S 2500", "Evolution S 2500" },
|
||||
{ "Evolution", "S 3000", "Evolution S 3000" },
|
||||
/* Pulsar M models */
|
||||
{ "PULSAR M", "2200", "Pulsar M 2200" },
|
||||
{ "PULSAR M", "3000", "Pulsar M 3000" },
|
||||
{ "PULSAR M", "3000 XL", "Pulsar M 3000 XL" },
|
||||
/* Eaton'ified names */
|
||||
{ "EX", "2200", "EX 2200" },
|
||||
{ "EX", "3000", "EX 3000" },
|
||||
{ "EX", "3000 XL", "EX 3000 XL" },
|
||||
/* Pulsar models */
|
||||
{ "Pulsar", "700", "Pulsar 700" },
|
||||
{ "Pulsar", "1000", "Pulsar 1000" },
|
||||
{ "Pulsar", "1500", "Pulsar 1500" },
|
||||
{ "Pulsar", "1000 RT2U", "Pulsar 1000 RT2U" },
|
||||
{ "Pulsar", "1500 RT2U", "Pulsar 1500 RT2U" },
|
||||
/* Eaton'ified names */
|
||||
{ "EX", "700", "EX 700" },
|
||||
{ "EX", "1000", "EX 1000" },
|
||||
{ "EX", "1500", "EX 1500" },
|
||||
{ "EX", "1000 RT2U", "EX 1000 RT2U" },
|
||||
{ "EX", "1500 RT2U", "EX 1500 RT2U" },
|
||||
/* Pulsar MX models */
|
||||
{ "PULSAR", "MX4000", "Pulsar MX 4000 RT" },
|
||||
{ "PULSAR", "MX5000", "Pulsar MX 5000 RT" },
|
||||
/* NOVA models */
|
||||
{ "NOVA AVR", "600", "NOVA 600 AVR" },
|
||||
{ "NOVA AVR", "625", "Nova AVR 625" },
|
||||
{ "NOVA AVR", "1100", "NOVA 1100 AVR" },
|
||||
{ "NOVA AVR", "1250", "Nova AVR 1250" },
|
||||
/* EXtreme C (EMEA) */
|
||||
{ "EXtreme", "700C", "Pulsar EXtreme 700C" },
|
||||
{ "EXtreme", "1000C", "Pulsar EXtreme 1000C" },
|
||||
{ "EXtreme", "1500C", "Pulsar EXtreme 1500C" },
|
||||
{ "EXtreme", "1500CCLA", "Pulsar EXtreme 1500C CLA" },
|
||||
{ "EXtreme", "2200C", "Pulsar EXtreme 2200C" },
|
||||
{ "EXtreme", "3200C", "Pulsar EXtreme 3200C" },
|
||||
/* EXtreme C (USA, aka "EX RT") */
|
||||
{ "EX", "700RT", "Pulsar EX 700 RT" },
|
||||
{ "EX", "1000RT", "Pulsar EX 1000 RT" },
|
||||
{ "EX", "1500RT", "Pulsar EX 1500 RT" },
|
||||
{ "EX", "2200RT", "Pulsar EX 2200 RT" },
|
||||
{ "EX", "3200RT", "Pulsar EX 3200 RT" },
|
||||
/* Comet EX RT three phased */
|
||||
{ "EX", "5RT31", "EX 5 RT 3:1" },
|
||||
{ "EX", "7RT31", "EX 7 RT 3:1" },
|
||||
{ "EX", "11RT31", "EX 11 RT 3:1" },
|
||||
/* Comet EX RT single phased */
|
||||
{ "EX", "5RT", "EX 5 RT" },
|
||||
{ "EX", "7RT", "EX 7 RT" },
|
||||
{ "EX", "11RT", "EX 11 RT" },
|
||||
/* Galaxy 3000 */
|
||||
{ "GALAXY", "3000_10", "Galaxy 3000 10 kVA" },
|
||||
{ "GALAXY", "3000_15", "Galaxy 3000 15 kVA" },
|
||||
{ "GALAXY", "3000_20", "Galaxy 3000 20 kVA" },
|
||||
{ "GALAXY", "3000_30", "Galaxy 3000 30 kVA" },
|
||||
|
||||
/* FIXME: To be completed (Comet, Galaxy, Esprit, ...) */
|
||||
|
||||
/* end of structure. */
|
||||
{ NULL, NULL, "Generic SHUT model" }
|
||||
};
|
||||
/* for lookup between HID values and NUT values*/
|
||||
typedef struct {
|
||||
long hid_value; /* HID value */
|
||||
char *nut_value; /* NUT value */
|
||||
} info_lkp_t;
|
||||
|
||||
/* Actual value lookup tables => should be fine for all Mfrs (TODO: validate it!) */
|
||||
/* --------------------------------------------------------------- */
|
||||
/* Lookup values between NUT and HID */
|
||||
/* --------------------------------------------------------------- */
|
||||
info_lkp_t onbatt_info[] = {
|
||||
{ 0, "OB" },
|
||||
{ 1, "OL" },
|
||||
{ 0, "NULL" }
|
||||
};
|
||||
info_lkp_t discharging_info[] = {
|
||||
{ 1, "DISCHRG" },
|
||||
{ 0, "NULL" }
|
||||
};
|
||||
info_lkp_t charging_info[] = {
|
||||
{ 1, "CHRG" },
|
||||
{ 0, "NULL" }
|
||||
};
|
||||
info_lkp_t lowbatt_info[] = {
|
||||
{ 1, "LB" },
|
||||
{ 0, "NULL" }
|
||||
};
|
||||
info_lkp_t overbatt_info[] = {
|
||||
{ 1, "OVER" },
|
||||
{ 0, "NULL" }
|
||||
};
|
||||
info_lkp_t replacebatt_info[] = {
|
||||
{ 1, "RB" },
|
||||
{ 0, "NULL" }
|
||||
};
|
||||
info_lkp_t shutdownimm_info[] = {
|
||||
{ 1, "LB" },
|
||||
{ 0, "NULL" }
|
||||
};
|
||||
info_lkp_t trim_info[] = {
|
||||
{ 1, "TRIM" },
|
||||
{ 0, "NULL" }
|
||||
};
|
||||
info_lkp_t boost_info[] = {
|
||||
{ 1, "BOOST" },
|
||||
{ 0, "NULL" }
|
||||
};
|
||||
/* TODO: add BYPASS, OFF, CAL */
|
||||
|
||||
info_lkp_t test_write_info[] = {
|
||||
{ 0, "No test" },
|
||||
{ 1, "Quick test" },
|
||||
{ 2, "Deep test" },
|
||||
{ 3, "Abort test" },
|
||||
{ 0, "NULL" }
|
||||
};
|
||||
info_lkp_t test_read_info[] = {
|
||||
{ 1, "Done and passed" },
|
||||
{ 2, "Done and warning" },
|
||||
{ 3, "Done and error" },
|
||||
{ 4, "Aborted" },
|
||||
{ 5, "In progress" },
|
||||
{ 6, "No test initiated" },
|
||||
{ 0, "NULL" }
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* Query Commands and their Mapping to INFO_ Variables */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
/* Structure defining how to query UPS for a variable and write
|
||||
information to INFO structure.
|
||||
*/
|
||||
typedef struct {
|
||||
const char *type; /* INFO_* element */
|
||||
int flags; /* INFO-element flags to set in addinfo */
|
||||
int length; /* INFO-element length of strings */
|
||||
const char *item_path; /* HID object (fully qualified string path) */
|
||||
const char fmt[6]; /* printf format string for INFO entry */
|
||||
const char *dfl; /* default value */
|
||||
unsigned long shut_flags; /* specific SHUT flags */
|
||||
info_lkp_t *hid2info; /* lookup table between HID and NUT values */
|
||||
} mge_info_item_t;
|
||||
|
||||
/* Array containing information to translate between UTalk and NUT info
|
||||
* NOTE:
|
||||
* - Array is terminated by element with type NULL.
|
||||
* - Essential INFO items (ups.{mfr, model, firmware, status} are
|
||||
* handled separately.
|
||||
* - Array is NOT const, since "shut_flags" can be changed.
|
||||
*/
|
||||
|
||||
/* FIXME: should be shared with mgehid.h */
|
||||
static mge_info_item_t mge_info[] = {
|
||||
/* Battery page */
|
||||
{ "battery.charge", 0, 0, "UPS.PowerSummary.RemainingCapacity", "%i", NULL, SHUT_FLAG_OK, NULL },
|
||||
{ "battery.charge.low", ST_FLAG_RW | ST_FLAG_STRING, 5, "UPS.PowerSummary.RemainingCapacityLimitSetting",
|
||||
"%ld", NULL, SHUT_FLAG_OK, NULL }, /* RW, to be caught first if exists... */
|
||||
{ "battery.charge.low", ST_FLAG_STRING, 5, "UPS.PowerSummary.RemainingCapacityLimit",
|
||||
"%ld", NULL, SHUT_FLAG_OK, NULL }, /* ... or Read only */
|
||||
{ "battery.runtime", 0, 0, "UPS.PowerSummary.RunTimeToEmpty", "%.0d", NULL, SHUT_FLAG_OK, NULL },
|
||||
/* UPS page */
|
||||
{ "ups.mfr", ST_FLAG_STRING, 20, NULL, "%s", "MGE UPS SYSTEMS", SHUT_FLAG_ABSENT | SHUT_FLAG_OK, NULL },
|
||||
{ "ups.load", 0, 0, "UPS.PowerSummary.PercentLoad", "%i", NULL, SHUT_FLAG_OK, NULL },
|
||||
{ "ups.timer.shutdown", ST_FLAG_RW | ST_FLAG_STRING, 5, "UPS.PowerSummary.DelayBeforeShutdown",
|
||||
"%ld", NULL, SHUT_FLAG_OK | SHUT_FLAG_DELAY, NULL },
|
||||
{ "ups.timer.reboot", ST_FLAG_RW | ST_FLAG_STRING, 5, "UPS.PowerSummary.DelayBeforeReboot",
|
||||
"%ld", NULL, SHUT_FLAG_OK | SHUT_FLAG_DELAY, NULL },
|
||||
{ "ups.timer.start", ST_FLAG_RW | ST_FLAG_STRING, 5, "UPS.PowerSummary.DelayBeforeStartup",
|
||||
"%ld", NULL, SHUT_FLAG_OK | SHUT_FLAG_DELAY, NULL },
|
||||
/* FIXME: miss ups.power */
|
||||
{ "ups.power.nominal", ST_FLAG_STRING, 5, "UPS.Flow.[4].ConfigApparentPower",
|
||||
"%i", NULL, SHUT_FLAG_OK, NULL },
|
||||
{ "ups.test.interval", ST_FLAG_RW | ST_FLAG_STRING, 5, "UPS.BatterySystem.Battery.TestPeriod",
|
||||
"%i", NULL, SHUT_FLAG_OK, NULL },
|
||||
{ "ups.test.result", ST_FLAG_STRING, 5, "UPS.BatterySystem.Battery.Test",
|
||||
"%i", NULL, SHUT_FLAG_OK, &test_read_info[0] },
|
||||
|
||||
/* Output page */
|
||||
{ "output.voltage", 0, 0, "UPS.PowerConverter.Output.Voltage", "%i", NULL, SHUT_FLAG_OK, NULL },
|
||||
{ "output.voltage.nominal", 0, 0, "UPS.PowerSummary.ConfigVoltage", "%i", NULL, SHUT_FLAG_OK, NULL },
|
||||
{ "output.current", 0, 0, "UPS.PowerSummary.Output.Current", "%i", NULL, SHUT_FLAG_OK, NULL },
|
||||
{ "output.frequency", 0, 0, "UPS.PowerConverter.Output.Frequency", "%i", NULL, SHUT_FLAG_OK, NULL },
|
||||
|
||||
/* Outlet page (using MGE UPS SYSTEMS - PowerShare technology) */
|
||||
/* TODO: add an iterative semantic [%x] to factorise outlets */
|
||||
{ "outlet.id", 0, 0, "UPS.OutletSystem.Outlet.[1].OutletID", "%i", NULL, SHUT_FLAG_OK, NULL },
|
||||
{ "outlet.desc", ST_FLAG_RW | ST_FLAG_STRING, 20, "UPS.OutletSystem.Outlet.[1].OutletID",
|
||||
"%s", "Main Outlet", SHUT_FLAG_ABSENT | SHUT_FLAG_OK, NULL },
|
||||
{ "outlet.switchable", 0, 0, "UPS.OutletSystem.Outlet.[1].PresentStatus.Switchable",
|
||||
"%i", NULL, SHUT_FLAG_OK, NULL },
|
||||
{ "outlet.1.id", 0, 0, "UPS.OutletSystem.Outlet.[2].OutletID", "%i", NULL, SHUT_FLAG_OK, NULL },
|
||||
{ "outlet.1.desc", ST_FLAG_RW | ST_FLAG_STRING, 20, "UPS.OutletSystem.Outlet.[2].OutletID",
|
||||
"%s", "PowerShare Outlet 1", SHUT_FLAG_ABSENT | SHUT_FLAG_OK, NULL },
|
||||
{ "outlet.1.switchable", 0, 0, "UPS.OutletSystem.Outlet.[2].PresentStatus.Switchable",
|
||||
"%i", NULL, SHUT_FLAG_OK, NULL },
|
||||
{ "outlet.1.switch", ST_FLAG_RW | ST_FLAG_STRING, 2, "UPS.OutletSystem.Outlet.[2].PresentStatus.SwitchOn/Off",
|
||||
"%i", NULL, SHUT_FLAG_OK, NULL },
|
||||
{ "outlet.1.autoswitch.charge.low", ST_FLAG_RW | ST_FLAG_STRING, 3,
|
||||
"UPS.OutletSystem.Outlet.[2].RemainingCapacityLimit", "%i", NULL, SHUT_FLAG_OK, NULL },
|
||||
{ "outlet.1.delay.shutdown", ST_FLAG_RW | ST_FLAG_STRING, 5,
|
||||
"UPS.OutletSystem.Outlet.[2].DelayBeforeShutdown", "%i", NULL, SHUT_FLAG_OK, NULL },
|
||||
{ "outlet.1.delay.start", ST_FLAG_RW | ST_FLAG_STRING, 5, "UPS.OutletSystem.Outlet.[2].DelayBeforeStartup", "%i", NULL, SHUT_FLAG_OK, NULL },
|
||||
{ "outlet.2.id", 0, 0, "UPS.OutletSystem.Outlet.[3].OutletID", "%i", NULL, SHUT_FLAG_OK, NULL },
|
||||
{ "outlet.2.desc", ST_FLAG_RW | ST_FLAG_STRING, 20, "UPS.OutletSystem.Outlet.[3].OutletID",
|
||||
"%s", "PowerShare Outlet 2", SHUT_FLAG_ABSENT | SHUT_FLAG_OK, NULL },
|
||||
{ "outlet.2.switchable", 0, 0, "UPS.OutletSystem.Outlet.[3].PresentStatus.Switchable",
|
||||
"%i", NULL, SHUT_FLAG_OK, NULL },
|
||||
{ "outlet.2.switch", ST_FLAG_RW | ST_FLAG_STRING, 2, "UPS.OutletSystem.Outlet.[3].PresentStatus.SwitchOn/Off",
|
||||
"%i", NULL, SHUT_FLAG_OK, NULL },
|
||||
{ "outlet.2.autoswitch.charge.low", ST_FLAG_RW | ST_FLAG_STRING, 3,
|
||||
"UPS.OutletSystem.Outlet.[3].RemainingCapacityLimit", "%i", NULL, SHUT_FLAG_OK, NULL },
|
||||
{ "outlet.2.delay.shutdown", ST_FLAG_RW | ST_FLAG_STRING, 5,
|
||||
"UPS.OutletSystem.Outlet.[3].DelayBeforeShutdown", "%i", NULL, SHUT_FLAG_OK, NULL },
|
||||
{ "outlet.2.delay.start", ST_FLAG_RW | ST_FLAG_STRING, 5,
|
||||
"UPS.OutletSystem.Outlet.[3].DelayBeforeStartup", "%i", NULL, SHUT_FLAG_OK, NULL },
|
||||
|
||||
/* Input page */
|
||||
{ "input.voltage", 0, 0, "UPS.PowerConverter.Input.[1].Voltage", "%i", NULL, SHUT_FLAG_OK, NULL },
|
||||
{ "input.frequency", 0, 0, "UPS.PowerConverter.Input.[1].Frequency", "%i", NULL, SHUT_FLAG_OK, NULL },
|
||||
|
||||
/* terminating element */
|
||||
{ NULL, 0, 0, "\0", "\0", NULL, 0, NULL }
|
||||
};
|
||||
|
||||
/* temporary usage code lookup */
|
||||
typedef struct {
|
||||
const char *usage_name;
|
||||
const uint32_t usage_code;
|
||||
} usage_lkp_t;
|
||||
|
||||
/* FIXME: share this data structure with libhid.c */
|
||||
static usage_lkp_t usage_lkp[] = {
|
||||
/* Power Device Page */
|
||||
{ "PresentStatus", 0x00840002 },
|
||||
{ "UPS", 0x00840004 },
|
||||
{ "BatterySystem", 0x00840010 },
|
||||
{ "Battery", 0x00840012 },
|
||||
{ "BatteryID", 0x00840013 },
|
||||
{ "PowerConverter", 0x00840016 },
|
||||
{ "OutletSystem", 0x00840018 },
|
||||
{ "Input", 0x0084001a },
|
||||
{ "Output", 0x0084001c },
|
||||
{ "Outlet", 0x00840020 },
|
||||
{ "OutletID", 0x00840021 },
|
||||
{ "PowerSummary", 0x00840024 },
|
||||
{ "Voltage", 0x00840030 },
|
||||
{ "Current", 0x00840031 },
|
||||
{ "Frequency", 0x00840032 },
|
||||
{ "PercentLoad", 0x00840035 },
|
||||
{ "ConfigVoltage", 0x00840040 },
|
||||
{ "ConfigCurrent", 0x00840041 },
|
||||
{ "ConfigFrequency", 0x00840042 },
|
||||
{ "ConfigApparentPower", 0x00840043 },
|
||||
{ "LowVoltageTransfer", 0x00840053 },
|
||||
{ "HighVoltageTransfer", 0x00840054 },
|
||||
{ "DelayBeforeReboot", 0x00840055 },
|
||||
{ "DelayBeforeStartup", 0x00840056 },
|
||||
{ "DelayBeforeShutdown", 0x00840057 },
|
||||
{ "Test", 0x00840058 },
|
||||
{ "Good", 0x00840061 },
|
||||
{ "Overload", 0x00840065 }, /* sic */
|
||||
{ "SwitchOn/Off", 0x0084006b },
|
||||
{ "Switchable", 0x0084006c },
|
||||
{ "Used", 0x0084006d },
|
||||
{ "Flow", 0x0084001e },
|
||||
/* Battery System Page */
|
||||
{ "RemainingCapacityLimit", 0x00850029 },
|
||||
{ "BelowRemainingCapacityLimit", 0x00850042 },
|
||||
{ "RemainingCapacity", 0x00850066 },
|
||||
{ "RunTimeToEmpty", 0x00850068 },
|
||||
{ "ACPresent", 0x008500d0 },
|
||||
{ "Charging", 0x00850044 },
|
||||
{ "Discharging", 0x00850045 },
|
||||
{ "NeedReplacement", 0x0085004b },
|
||||
/* MGE UPS SYSTEMS Page */
|
||||
{ "iModel", 0xffff00f0 },
|
||||
{ "RemainingCapacityLimitSetting", 0xffff004d },
|
||||
{ "TestPeriod", 0xffff0001 },
|
||||
|
||||
{ "\0", 0x0 }
|
||||
};
|
||||
|
||||
/* SHUT / HID functions Prototypes */
|
||||
|
||||
int shut_ups_start(void);
|
||||
u_char shut_checksum(const u_char *buf, int bufsize);
|
||||
int shut_token_send(u_char token);
|
||||
int shut_packet_send (hid_data_t *hdata, int datalen, u_char token);
|
||||
int shut_packet_recv (u_char *Buf, int datalen);
|
||||
|
||||
int shut_get_descriptor(int desctype, u_char *pkt, int reportlen);
|
||||
int shut_get_string(int strindex, char *string, int stringlen);
|
||||
int shut_get_report(int id, u_char *pkt, int reportlen);
|
||||
int shut_set_report(int id, u_char *pkt, int reportlen);
|
||||
int shut_identify_ups (void);
|
||||
int shut_wait_ack (void);
|
||||
void shut_ups_status(void);
|
||||
|
||||
int hid_init_device(void);
|
||||
char *get_model_name(char *iProduct, char *iModel);
|
||||
int hid_lookup_usage(char *name);
|
||||
int hid_get_value(const char *item_path);
|
||||
int hid_set_value(const char *varname, const char *val);
|
||||
u_short lookup_path(const char *HIDpath, HIDData_t *data);
|
||||
|
||||
int instcmd(const char *cmdname, const char *extra);
|
||||
void setline(int set);
|
||||
int serial_read (int read_timeout, u_char *readbuf);
|
||||
int serial_send(u_char *buf, int len);
|
||||
|
||||
void make_string(u_char *buf, int datalen, char *string);
|
||||
|
||||
|
||||
mge_info_item_t *shut_find_info(const char *varname);
|
||||
924
drivers/mge-utalk.c
Normal file
924
drivers/mge-utalk.c
Normal file
|
|
@ -0,0 +1,924 @@
|
|||
/* mge-utalk.c - monitor MGE UPS for NUT with UTalk protocol
|
||||
*
|
||||
* Copyright (C) 2002 - 2005
|
||||
* Arnaud Quette <arnaud.quette@gmail.com>
|
||||
* Hans Ekkehard Plesser <hans.plesser@itf.nlh.no>
|
||||
* Martin Loyer <martin@ouifi.net>
|
||||
* Patrick Agrain <patrick.agrain@alcatel.fr>
|
||||
* Nicholas Reilly <nreilly@magma.ca>
|
||||
* Dave Abbott <d.abbott@dcs.shef.ac.uk>
|
||||
* Marek Kralewski <marek@mercy49.de>
|
||||
*
|
||||
* This driver is a collaborative effort by the above people,
|
||||
* Sponsored by MGE UPS SYSTEMS
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* IMPLEMENTATION DETAILS
|
||||
*
|
||||
* Not all UTalk models provide all possible information, settings and commands.
|
||||
* mge-utalk checks on startup which variables and commands are available from
|
||||
* the UPS, and re-reads these regularly. Thus, startup is a bit slow, but this
|
||||
* should not matter much.
|
||||
*
|
||||
* mge-utalk.h defines a struct array that tells the driver how to read
|
||||
* variables from the UPS and publish them as NUT data.
|
||||
*
|
||||
* "ups.status" variable is not included in this array, since it contains
|
||||
* information that requires several calls to the UPS and more advanced analysis
|
||||
* of the reponses. The function get_ups_status does this job.
|
||||
*
|
||||
* Note that MGE enumerates the status "bits" from right to left,
|
||||
* i.e., if buf[] contains the reponse to command "Ss" (read system status),
|
||||
* then buf[0] contains "bit" Ss.1.7 (General alarm), while buf[7] contains
|
||||
* "bit" Ss.1.0 (Load unprotected).
|
||||
*
|
||||
* enable_ups_comm() is called before each attempt to read/write data
|
||||
* from/to the UPS to re synchronise the communication.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include "timehead.h"
|
||||
#include "main.h"
|
||||
#include "serial.h"
|
||||
#include "mge-utalk.h"
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* Define "technical" constants */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
#define DRIVER_NAME "MGE UPS SYSTEMS/U-Talk driver"
|
||||
#define DRIVER_VERSION "0.89"
|
||||
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Arnaud Quette <ArnaudQuette@gmail.com>\n" \
|
||||
"Hans Ekkehard Plesser <hans.plesser@itf.nlh.no>\n" \
|
||||
"Martin Loyer <martin@ouifi.net>\n" \
|
||||
"Patrick Agrain <patrick.agrain@alcatel.fr>\n" \
|
||||
"Nicholas Reilly <nreilly@magma.ca>\n" \
|
||||
"Dave Abbott <d.abbott@dcs.shef.ac.uk>\n" \
|
||||
"Marek Kralewski <marek@mercy49.de>",
|
||||
DRV_STABLE,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
/* delay after sending each char to UPS (in MICROSECONDS) */
|
||||
#define MGE_CHAR_DELAY 0
|
||||
|
||||
/* delay after command, before reading UPS reply (in MICROSECONDS) */
|
||||
#define MGE_REPLY_DELAY 1000
|
||||
|
||||
/* delay after enable_ups_comm */
|
||||
#define MGE_CONNECT_DELAY 500000
|
||||
|
||||
#define MGE_COMMAND_ENDCHAR "\r\n" /* some UPS need \r and \n */
|
||||
#define MGE_REPLY_ENDCHAR '\r'
|
||||
#define MGE_REPLY_IGNCHAR "\r\n"
|
||||
|
||||
#define MAXTRIES 10 /* max number of connect tries */
|
||||
#define BUFFLEN 256
|
||||
|
||||
#define SD_RETURN 0
|
||||
#define SD_STAYOFF 1
|
||||
|
||||
int sdtype = SD_RETURN;
|
||||
static time_t lastpoll; /* Timestamp the last polling */
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* Structure with information about UPS */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
static struct {
|
||||
int MultTab;
|
||||
int LowBatt; /* percent */
|
||||
int OnDelay; /* minutes */
|
||||
int OffDelay; /* seconds */
|
||||
} mge_ups = { 0, DEFAULT_LOWBATT, DEFAULT_ONDELAY, DEFAULT_OFFDELAY };
|
||||
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* Declaration of internal functions */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
static int instcmd(const char *cmdname, const char *extra);
|
||||
static int setvar(const char *varname, const char *val);
|
||||
static void enable_ups_comm(void);
|
||||
static void disable_ups_comm(void);
|
||||
static void extract_info(const char *buf, const mge_info_item_t *mge,
|
||||
char *infostr, int infolen);
|
||||
static const char *info_variable_cmd(const char *type);
|
||||
static bool_t info_variable_ok(const char *type);
|
||||
static int get_ups_status(void);
|
||||
static int mge_command(char *reply, int replylen, const char *fmt, ...);
|
||||
static void format_model_name(char *model);
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* UPS Driver Functions */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
char temp[BUFFLEN];
|
||||
|
||||
snprintf(temp, sizeof(temp),
|
||||
"Low battery level, in %% (default = %3d)",
|
||||
DEFAULT_LOWBATT);
|
||||
addvar (VAR_VALUE, "LowBatt", temp);
|
||||
|
||||
snprintf(temp, sizeof(temp),
|
||||
"Delay before startup, in minutes (default = %3d)",
|
||||
DEFAULT_ONDELAY);
|
||||
addvar (VAR_VALUE, "OnDelay", temp);
|
||||
|
||||
snprintf(temp, sizeof(temp),
|
||||
"Delay before shutdown, in seconds (default = %3d)",
|
||||
DEFAULT_OFFDELAY);
|
||||
addvar (VAR_VALUE, "OffDelay", temp);
|
||||
|
||||
addvar(VAR_FLAG, "oldmac", "Enable Oldworld Apple Macintosh support");
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
char buf[BUFFLEN];
|
||||
int RTS = TIOCM_RTS;
|
||||
|
||||
upsfd = ser_open(device_path);
|
||||
ser_set_speed(upsfd, device_path, B2400);
|
||||
|
||||
/* read command line/conf variable that affect comm. */
|
||||
if (testvar ("oldmac"))
|
||||
RTS = ~TIOCM_RTS;
|
||||
|
||||
/* Init serial line */
|
||||
ioctl(upsfd, TIOCMBIC, &RTS);
|
||||
enable_ups_comm();
|
||||
|
||||
/* Try to set "Low Battery Level" (if supported and given) */
|
||||
if (getval ("lowbatt"))
|
||||
{
|
||||
mge_ups.LowBatt = atoi (getval ("lowbatt"));
|
||||
/* Set the value in the UPS */
|
||||
mge_command(buf, sizeof(buf), "Bl %d", mge_ups.LowBatt);
|
||||
if(!strcmp(buf, "OK"))
|
||||
upsdebugx(1, "Low Battery Level set to %d%%", mge_ups.LowBatt);
|
||||
else
|
||||
upsdebugx(1, "initups: Low Battery Level cannot be set");
|
||||
}
|
||||
|
||||
/* Try to set "ON delay" (if supported and given) */
|
||||
if (getval ("ondelay"))
|
||||
{
|
||||
mge_ups.OnDelay = atoi (getval ("ondelay"));
|
||||
/* Set the value in the UPS */
|
||||
mge_command(buf, sizeof(buf), "Sm %d", mge_ups.OnDelay);
|
||||
if(!strcmp(buf, "OK"))
|
||||
upsdebugx(1, "ON delay set to %d min", mge_ups.OnDelay);
|
||||
else
|
||||
upsdebugx(1, "initups: OnDelay unavailable");
|
||||
}
|
||||
|
||||
/* Try to set "OFF delay" (if supported and given) */
|
||||
if (getval ("offdelay"))
|
||||
{
|
||||
mge_ups.OffDelay = atoi (getval ("offdelay"));
|
||||
/* Set the value in the UPS */
|
||||
mge_command(buf, sizeof(buf), "Sn %d", mge_ups.OffDelay);
|
||||
if(!strcmp(buf, "OK"))
|
||||
upsdebugx(1, "OFF delay set to %d sec", mge_ups.OffDelay);
|
||||
else
|
||||
upsdebugx(1, "initups: OffDelay unavailable");
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
void upsdrv_initinfo(void)
|
||||
{
|
||||
char buf[BUFFLEN];
|
||||
char *model = NULL;
|
||||
char *firmware = NULL;
|
||||
char *p;
|
||||
char *v = NULL; /* for parsing Si output, get Version ID */
|
||||
int table;
|
||||
int tries;
|
||||
int status_ok;
|
||||
int bytes_rcvd;
|
||||
int si_data1 = 0;
|
||||
int si_data2 = 0;
|
||||
mge_info_item_t *item;
|
||||
mge_model_info_t *legacy_model;
|
||||
char infostr[32];
|
||||
int chars_rcvd;
|
||||
|
||||
/* manufacturer -------------------------------------------- */
|
||||
dstate_setinfo("ups.mfr", "MGE UPS SYSTEMS");
|
||||
dstate_setinfo("driver.version.internal", "%s", DRIVER_VERSION);
|
||||
|
||||
/* loop until we have at status */
|
||||
tries = 0;
|
||||
do {
|
||||
printf(".");
|
||||
|
||||
/* get model information in ASCII string form: <Family> <Model> <Firmware> */
|
||||
bytes_rcvd = mge_command(buf, sizeof(buf), "Si 1");
|
||||
|
||||
if(bytes_rcvd > 0 && buf[0] != '?') {
|
||||
dstate_setinfo("ups.id", "%s", buf); /* raw id */
|
||||
|
||||
model = buf;
|
||||
p = strrchr(buf, ' ');
|
||||
|
||||
if ( p != NULL ) {
|
||||
*p = '\0';
|
||||
firmware = p+1;
|
||||
}
|
||||
|
||||
if( firmware && strlen(firmware) < 1 )
|
||||
firmware = NULL; /* no firmware information */
|
||||
}
|
||||
else
|
||||
{
|
||||
upsdebugx(1, "initinfo: 'Si 1' unavailable, switching to 'Si' command");
|
||||
|
||||
/* get model information, numbered form, : <Model ID> <Version ID> <Firmware> */
|
||||
bytes_rcvd = mge_command(buf, sizeof(buf), "Si");
|
||||
|
||||
if(bytes_rcvd > 0 && buf[0] != '?') {
|
||||
upsdebugx(1, "initinfo: Si == >%s<", buf);
|
||||
|
||||
printf("\nCAUTION : This is an older model. It may not support too much polling.\nPlease read man mge-utalk and use pollinterval\n");
|
||||
|
||||
p = strchr(buf, ' ');
|
||||
|
||||
if ( p != NULL ) {
|
||||
*p = '\0';
|
||||
si_data1 = atoi(buf);
|
||||
v = p+1;
|
||||
}
|
||||
|
||||
p = strchr(v, ' ');
|
||||
|
||||
if ( p != NULL ) {
|
||||
*p = '\0';
|
||||
si_data2 = atoi(v);
|
||||
}
|
||||
|
||||
/* Parsing legacy model table in order to found it */
|
||||
for ( legacy_model = mge_model ; legacy_model->name != NULL ; legacy_model++ ) {
|
||||
if(legacy_model->Data1 == si_data1 && legacy_model->Data2 == si_data2){
|
||||
model = (char *)legacy_model->name;
|
||||
upsdebugx(1, "initinfo: UPS model == >%s<", model);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( model == NULL )
|
||||
printf("No model found by that model and version ID\nPlease contact us with UPS model, name and reminder info\nReminder info : Data1=%i , Data2=%i\n", si_data1, si_data2);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if ( model ) {
|
||||
format_model_name(model);
|
||||
dstate_setinfo("ups.model", "%s", model);
|
||||
}
|
||||
|
||||
if ( firmware && strcmp(firmware, ""))
|
||||
dstate_setinfo("ups.firmware", "%s", firmware);
|
||||
else
|
||||
dstate_setinfo("ups.firmware", "unknown");
|
||||
|
||||
/* multiplier table */
|
||||
/* <protocol level> <multiplier table> */
|
||||
bytes_rcvd = mge_command(buf, sizeof(buf), "Ai");
|
||||
|
||||
if (bytes_rcvd > 0 && buf[0] != '?') {
|
||||
p = strchr(buf, ' ');
|
||||
if ( p != NULL ) {
|
||||
table = atoi(p + 1);
|
||||
if ( 0 < table && table < 4 )
|
||||
mge_ups.MultTab = table;
|
||||
}
|
||||
}
|
||||
|
||||
/* status --- try only system status, to get the really important
|
||||
* information (OL, OB, LB); all else is added later by updateinfo */
|
||||
status_ok = get_ups_status();
|
||||
|
||||
} while ( (!status_ok) && (tries++ < MAXTRIES) && (exit_flag != 0) );
|
||||
|
||||
if ( tries == MAXTRIES && !status_ok )
|
||||
fatalx(EXIT_FAILURE, "Could not get status from UPS.");
|
||||
|
||||
if ( mge_ups.MultTab == 0 )
|
||||
upslogx(LOG_WARNING, "Could not get multiplier table: using raw readings.");
|
||||
|
||||
/* all other variables ------------------------------------ */
|
||||
for ( item = mge_info ; item->type != NULL ; item++ ) {
|
||||
|
||||
/* Check if we are asked to stop (reactivity++) */
|
||||
if (exit_flag != 0)
|
||||
return;
|
||||
|
||||
/* send request, read answer */
|
||||
chars_rcvd = mge_command(buf, sizeof(buf), item->cmd);
|
||||
|
||||
if ( chars_rcvd < 1 || buf[0] == '?' ) {
|
||||
item->ok = FALSE;
|
||||
upsdebugx(1, "initinfo: %s unavailable", item->type);
|
||||
} else {
|
||||
item->ok = TRUE;
|
||||
extract_info(buf, item, infostr, sizeof(infostr));
|
||||
dstate_setinfo(item->type, "%s", infostr);
|
||||
dstate_setflags(item->type, item->flags);
|
||||
upsdebugx(1, "initinfo: %s == >%s<", item->type, infostr);
|
||||
|
||||
/* Set max length for strings */
|
||||
if (item->flags & ST_FLAG_STRING)
|
||||
dstate_setaux(item->type, item->length);
|
||||
}
|
||||
} /* for item */
|
||||
|
||||
/* store timestamp */
|
||||
lastpoll = time(NULL);
|
||||
|
||||
/* commands ----------------------------------------------- */
|
||||
/* FIXME: check if available before adding! */
|
||||
dstate_addcmd("load.off");
|
||||
dstate_addcmd("load.on");
|
||||
dstate_addcmd("shutdown.return");
|
||||
dstate_addcmd("shutdown.stayoff");
|
||||
dstate_addcmd("test.panel.start");
|
||||
dstate_addcmd("test.battery.start");
|
||||
dstate_addcmd("bypass.start");
|
||||
dstate_addcmd("bypass.stop");
|
||||
|
||||
/* install handlers */
|
||||
upsh.setvar = setvar;
|
||||
upsh.instcmd = instcmd;
|
||||
|
||||
printf("Detected %s on %s\n", dstate_getinfo("ups.model"), device_path);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
char buf[BUFFLEN];
|
||||
char infostr[32];
|
||||
int status_ok;
|
||||
int bytes_rcvd;
|
||||
mge_info_item_t *item;
|
||||
|
||||
/* make sure that communication is enabled */
|
||||
enable_ups_comm();
|
||||
|
||||
/* update status */
|
||||
status_ok = get_ups_status(); /* only sys status is critical */
|
||||
if ( !status_ok ) {
|
||||
dstate_datastale();
|
||||
upslogx(LOG_NOTICE, "updateinfo: Cannot update system status");
|
||||
/* try to re enable communication */
|
||||
disable_ups_comm();
|
||||
enable_ups_comm();
|
||||
} else {
|
||||
dstate_dataok();
|
||||
}
|
||||
|
||||
/* Don't overload old units (at startup) */
|
||||
if ( (unsigned int)time(NULL) <= (unsigned int)(lastpoll + poll_interval) )
|
||||
return;
|
||||
|
||||
/* update all other ok variables */
|
||||
for ( item = mge_info ; item->type != NULL ; item++ ) {
|
||||
/* Check if we are asked to stop (reactivity++) */
|
||||
if (exit_flag != 0)
|
||||
return;
|
||||
|
||||
if ( item->ok ) {
|
||||
/* send request, read answer */
|
||||
bytes_rcvd = mge_command(buf, sizeof(buf), item->cmd);
|
||||
|
||||
if ( bytes_rcvd > 0 && buf[0] != '?' ) {
|
||||
extract_info(buf, item, infostr, sizeof(infostr));
|
||||
dstate_setinfo(item->type, "%s", infostr);
|
||||
upsdebugx(2, "updateinfo: %s == >%s<", item->type, infostr);
|
||||
dstate_dataok();
|
||||
} else {
|
||||
dstate_datastale();
|
||||
upslogx(LOG_NOTICE, "updateinfo: Cannot update %s", item->type);
|
||||
/* try to re enable communication */
|
||||
disable_ups_comm();
|
||||
enable_ups_comm();
|
||||
}
|
||||
} /* if item->ok */
|
||||
}
|
||||
|
||||
/* store timestamp */
|
||||
lastpoll = time(NULL);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
char buf[BUFFLEN];
|
||||
/* static time_t lastcmd = 0; */
|
||||
|
||||
if (sdtype == SD_RETURN) {
|
||||
/* enable automatic restart */
|
||||
mge_command(buf, sizeof(buf), "Sx 5");
|
||||
|
||||
upslogx(LOG_INFO, "UPS response to Automatic Restart was %s", buf);
|
||||
}
|
||||
|
||||
/* Only call the effective shutoff if restart is ok */
|
||||
/* or if we need only a stayoff... */
|
||||
if (!strcmp(buf, "OK") || (sdtype == SD_STAYOFF)) {
|
||||
/* shutdown UPS */
|
||||
mge_command(buf, sizeof(buf), "Sx 0");
|
||||
|
||||
upslogx(LOG_INFO, "UPS response to Shutdown was %s", buf);
|
||||
}
|
||||
/* if(strcmp(buf, "OK")) */
|
||||
|
||||
/* call the cleanup to disable/close the comm link */
|
||||
upsdrv_cleanup();
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* Internal Functions */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
/* deal with truncated model names */
|
||||
void format_model_name(char *model)
|
||||
{
|
||||
upsdebugx(2, "Got model name: %s", model);
|
||||
|
||||
if(!strncmp(model, "Evolutio", 8))
|
||||
sprintf(model, "Evolution %i", atoi(strchr(model, ' ')));
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
/* handler for commands to be sent to UPS */
|
||||
int instcmd(const char *cmdname, const char *extra)
|
||||
{
|
||||
char temp[BUFFLEN];
|
||||
|
||||
/* Start battery test */
|
||||
if (!strcasecmp(cmdname, "test.battery.start"))
|
||||
{
|
||||
mge_command(temp, sizeof(temp), "Bx 1");
|
||||
upsdebugx(2, "UPS response to %s was %s", cmdname, temp);
|
||||
|
||||
if(strcmp(temp, "OK"))
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
else
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
/* Start front panel test */
|
||||
if (!strcasecmp(cmdname, "test.panel.start"))
|
||||
{
|
||||
mge_command(temp, sizeof(temp), "Sx 129");
|
||||
upsdebugx(2, "UPS response to %s was %s", cmdname, temp);
|
||||
|
||||
if(strcmp(temp, "OK"))
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
else
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
/* Shutdown UPS */
|
||||
if (!strcasecmp(cmdname, "shutdown.stayoff"))
|
||||
{
|
||||
sdtype = SD_STAYOFF;
|
||||
upsdrv_shutdown();
|
||||
}
|
||||
|
||||
if (!strcasecmp(cmdname, "shutdown.return"))
|
||||
{
|
||||
sdtype = SD_RETURN;
|
||||
upsdrv_shutdown();
|
||||
}
|
||||
|
||||
/* Power Off [all] plugs */
|
||||
if (!strcasecmp(cmdname, "load.off"))
|
||||
{
|
||||
/* TODO: Powershare (per plug) control */
|
||||
mge_command(temp, sizeof(temp), "Wy 65535");
|
||||
upsdebugx(2, "UPS response to Select All Plugs was %s", temp);
|
||||
|
||||
if(strcmp(temp, "OK"))
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
else
|
||||
{
|
||||
mge_command(temp, sizeof(temp), "Wx 0");
|
||||
upsdebugx(2, "UPS response to %s was %s", cmdname, temp);
|
||||
if(strcmp(temp, "OK"))
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
else
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
}
|
||||
|
||||
/* Power On all plugs */
|
||||
if (!strcasecmp(cmdname, "load.on"))
|
||||
{
|
||||
/* TODO: add per plug control */
|
||||
mge_command(temp, sizeof(temp), "Wy 65535");
|
||||
upsdebugx(2, "UPS response to Select All Plugs was %s", temp);
|
||||
|
||||
if(strcmp(temp, "OK"))
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
else
|
||||
{
|
||||
mge_command(temp, sizeof(temp), "Wx 1");
|
||||
upsdebugx(2, "UPS response to %s was %s", cmdname, temp);
|
||||
if(strcmp(temp, "OK"))
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
else
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
}
|
||||
|
||||
/* Switch on/off Maintenance Bypass */
|
||||
if ((!strcasecmp(cmdname, "bypass.start"))
|
||||
|| (!strcasecmp(cmdname, "bypass.stop")))
|
||||
{
|
||||
/* TODO: add control on bypass value */
|
||||
/* read maintenance bypass status */
|
||||
if(mge_command(temp, sizeof(temp), "Ps") > 0)
|
||||
{
|
||||
if (temp[0] == '1')
|
||||
{
|
||||
/* Disable Maintenance Bypass */
|
||||
mge_command(temp, sizeof(temp), "Px 2");
|
||||
upsdebugx(2, "UPS response to Select All Plugs was %s", temp);
|
||||
} else
|
||||
{
|
||||
/* Enable Maintenance Bypass */
|
||||
mge_command(temp, sizeof(temp), "Px 3");
|
||||
}
|
||||
|
||||
upsdebugx(2, "UPS response to %s was %s", cmdname, temp);
|
||||
|
||||
if(strcmp(temp, "OK"))
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
else
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
}
|
||||
|
||||
upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
/* handler for settable variables in UPS*/
|
||||
int setvar(const char *varname, const char *val)
|
||||
{
|
||||
char temp[BUFFLEN];
|
||||
char cmd[15];
|
||||
|
||||
/* TODO : add some controls */
|
||||
|
||||
if(info_variable_ok(varname))
|
||||
{
|
||||
/* format command */
|
||||
snprintf(cmd, sizeof(cmd), "%s", info_variable_cmd(varname));
|
||||
sprintf(strchr(cmd, '?'), "%s", val);
|
||||
|
||||
/* Execute command */
|
||||
mge_command(temp, sizeof(temp), cmd);
|
||||
upslogx(LOG_INFO, "setvar: UPS response to Set %s to %s was %s", varname, val, temp);
|
||||
} else
|
||||
upsdebugx(1, "setvar: Variable %s not supported by UPS", varname);
|
||||
|
||||
return STAT_SET_UNKNOWN;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
/* disable communication with UPS to avoid interference with
|
||||
* kernel serial init at boot time (ie with V24 init) */
|
||||
static void disable_ups_comm(void)
|
||||
{
|
||||
upsdebugx(1, "disable_ups_comm()");
|
||||
ser_flush_in(upsfd, "?\r\n", 0);
|
||||
usleep(MGE_CONNECT_DELAY);
|
||||
mge_command(NULL, 0, "Ax 0");
|
||||
}
|
||||
|
||||
/* enable communication with UPS */
|
||||
static void enable_ups_comm(void)
|
||||
{
|
||||
char buf[8];
|
||||
|
||||
/* only enable communication if needed! */
|
||||
if ( mge_command(buf, 8, "Si") <= 0)
|
||||
{
|
||||
mge_command(NULL, 0, "Z"); /* send Z twice --- speeds up re-connect */
|
||||
mge_command(NULL, 0, "Z");
|
||||
mge_command(NULL, 0, "Ax 1");
|
||||
usleep(MGE_CONNECT_DELAY);
|
||||
}
|
||||
|
||||
ser_flush_in(upsfd, "?\r\n", nut_debug_level);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
/* extract information from buffer
|
||||
in: buf : reply from UPS
|
||||
item : INFO item queried
|
||||
out: infostr: to be placed in INFO_ variable
|
||||
NOTE: buf="?" must be handled before calling extract_info
|
||||
buf is changed inspite of const !!!!!
|
||||
*/
|
||||
static void extract_info(const char *buf, const mge_info_item_t *item,
|
||||
char *infostr, int infolen)
|
||||
{
|
||||
/* initialize info string */
|
||||
infostr[0] = '\0';
|
||||
|
||||
/* write into infostr with proper formatting */
|
||||
if ( strpbrk(item->fmt, "feEgG") ) { /* float */
|
||||
snprintf(infostr, infolen, item->fmt,
|
||||
multiplier[mge_ups.MultTab][item->unit] * atof(buf));
|
||||
} else if ( strpbrk(item->fmt, "dioxXuc") ) { /* int */
|
||||
snprintf(infostr, infolen, item->fmt,
|
||||
(int) (multiplier[mge_ups.MultTab][item->unit] * atof(buf)));
|
||||
} else {
|
||||
snprintf(infostr, infolen, item->fmt, buf);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
/* get system status, at least: OB, OL, LB
|
||||
calls set_status appropriately
|
||||
tries MAXTRIES times
|
||||
returns non-nil if successful
|
||||
|
||||
NOTE: MGE counts bytes/chars the opposite way as C,
|
||||
see mge-utalk manpage. If status commands send two
|
||||
data items, these are separated by a space, so
|
||||
the elements of the second item are in buf[16..9].
|
||||
*/
|
||||
|
||||
static int get_ups_status(void)
|
||||
{
|
||||
char buf[BUFFLEN];
|
||||
int rb_set= FALSE; /* has RB flag been set ? */
|
||||
int over_set= FALSE; /* has OVER flag been set ? */
|
||||
int tries = 0;
|
||||
int ok = FALSE;
|
||||
int bytes_rcvd = 0;
|
||||
|
||||
do {
|
||||
/* Check if we are asked to stop (reactivity++) */
|
||||
if (exit_flag != 0)
|
||||
return FALSE;
|
||||
|
||||
/* must clear status buffer before each round */
|
||||
status_init();
|
||||
|
||||
/* system status */
|
||||
/* FIXME: some old units sometimes return "Syst Stat >1<"
|
||||
resulting in an temporary OB status */
|
||||
bytes_rcvd = mge_command(buf, sizeof(buf), "Ss");
|
||||
upsdebugx(1, "Syst Stat >%s<", buf);
|
||||
if ( bytes_rcvd > 0 && strlen(buf) > 7 ) {
|
||||
ok = TRUE;
|
||||
if (buf[6] == '1') {
|
||||
over_set = TRUE;
|
||||
status_set("OVER");
|
||||
}
|
||||
if (buf[5] == '1')
|
||||
status_set("OB");
|
||||
else
|
||||
status_set("OL");
|
||||
|
||||
if (buf[4] == '1')
|
||||
status_set("LB");
|
||||
|
||||
if (buf[3] == '1') {
|
||||
rb_set = TRUE;
|
||||
status_set("RB");
|
||||
}
|
||||
/* buf[2] not used */
|
||||
if (buf[1] == '1')
|
||||
status_set("COMMFAULT"); /* self-invented */
|
||||
/* FIXME: better to call datastale()?! */
|
||||
if (buf[0] == '1')
|
||||
status_set("ALARM"); /* self-invented */
|
||||
/* FIXME: better to use ups.alarm */
|
||||
} /* if strlen */
|
||||
|
||||
/* battery status */
|
||||
mge_command(buf, sizeof(buf), "Bs");
|
||||
upsdebugx(1, "Batt Stat >%s<", buf);
|
||||
if ( strlen(buf) > 7 ) {
|
||||
if ( !rb_set && ( buf[7] == '1' || buf[3] == '1' ) )
|
||||
status_set("RB");
|
||||
|
||||
if (buf[1] == '1')
|
||||
status_set("CHRG");
|
||||
|
||||
if (buf[0] == '1')
|
||||
status_set("DISCHRG");
|
||||
} /* if strlen */
|
||||
|
||||
/* load status */
|
||||
mge_command(buf, sizeof(buf), "Ls");
|
||||
upsdebugx(1, "Load Stat >%s<", buf);
|
||||
if ( strlen(buf) > 7 ) {
|
||||
if (buf[4] == '1')
|
||||
status_set("BOOST");
|
||||
|
||||
if ( !over_set && ( buf[3] == '1' ) )
|
||||
status_set("OVER");
|
||||
|
||||
if (buf[2] == '1')
|
||||
status_set("TRIM");
|
||||
} /* if strlen */
|
||||
|
||||
if ( strlen(buf) > 15 ) { /* second "byte", skip <SP> */
|
||||
if (buf[16] == '1') {
|
||||
status_set("OB");
|
||||
status_set("LB");
|
||||
}
|
||||
|
||||
/* FIXME: to be checked (MUST be buf[8]) !! */
|
||||
/* if ( !(buf[9] == '1') ) */
|
||||
/* This is not the OFF status!
|
||||
if ( !(buf[8] == '1') )
|
||||
status_set("OFF"); */
|
||||
} /* if strlen */
|
||||
|
||||
/* Bypass status */
|
||||
mge_command(buf, sizeof(buf), "Ps");
|
||||
upsdebugx(1, "Bypass Stat >%s<", buf);
|
||||
if ( strlen(buf) > 7 ) {
|
||||
/* FIXME: extend ups.status for BYPASS: */
|
||||
/* Manual Bypass */
|
||||
if (buf[7] == '1')
|
||||
status_set("BYPASS");
|
||||
/* Automatic Bypass */
|
||||
if (buf[6] == '1')
|
||||
status_set("BYPASS");
|
||||
} /* if strlen */
|
||||
|
||||
} while ( !ok && tries++ < MAXTRIES );
|
||||
|
||||
status_commit();
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
/* return proper variable "ok" given INFO_ type */
|
||||
|
||||
static bool_t info_variable_ok(const char *type)
|
||||
{
|
||||
mge_info_item_t *item = mge_info ;
|
||||
|
||||
while ( strcasecmp(item->type, type ))
|
||||
item++;
|
||||
|
||||
return item->ok;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
/* return proper variable "cmd" given INFO_ type */
|
||||
|
||||
static const char *info_variable_cmd(const char *type)
|
||||
{
|
||||
mge_info_item_t *item = mge_info ;
|
||||
|
||||
while ( strcasecmp(item->type, type ))
|
||||
item++;
|
||||
|
||||
return item->cmd;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
/* send command to UPS and read reply if requested
|
||||
|
||||
reply : buffer for reply, NULL if no reply expected
|
||||
replylen: length of buffer reply
|
||||
fmt : format string, followed by optional data for command
|
||||
|
||||
returns : no of chars received, -1 if error
|
||||
*/
|
||||
static int mge_command(char *reply, int replylen, const char *fmt, ...)
|
||||
{
|
||||
const char *p;
|
||||
char command[BUFFLEN];
|
||||
int bytes_sent = 0;
|
||||
int bytes_rcvd = 0;
|
||||
int ret;
|
||||
va_list ap;
|
||||
|
||||
/* build command string */
|
||||
va_start(ap, fmt);
|
||||
|
||||
ret = vsnprintf(command, sizeof(command), fmt, ap);
|
||||
|
||||
if ((ret < 1) || (ret >= (int) sizeof(command)))
|
||||
upsdebugx(4, "mge_command: command truncated");
|
||||
|
||||
va_end(ap);
|
||||
|
||||
/* Delay a bit to avoid overlap of a previous answer */
|
||||
usleep(100000);
|
||||
|
||||
/* flush received, unread data */
|
||||
tcflush(upsfd, TCIFLUSH);
|
||||
|
||||
/* send command */
|
||||
for (p = command; *p; p++) {
|
||||
if ( isprint(*p & 0xFF) )
|
||||
upsdebugx(4, "mge_command: sending [%c]", *p);
|
||||
else
|
||||
upsdebugx(4, "mge_command: sending [%02X]", *p);
|
||||
|
||||
if (write(upsfd, p, 1) != 1)
|
||||
return -1;
|
||||
|
||||
bytes_sent++;
|
||||
usleep(MGE_CHAR_DELAY);
|
||||
}
|
||||
|
||||
/* send terminating string */
|
||||
if (MGE_COMMAND_ENDCHAR) {
|
||||
for (p = MGE_COMMAND_ENDCHAR; *p; p++) {
|
||||
if ( isprint(*p & 0xFF) )
|
||||
upsdebugx(4, "mge_command: sending [%c]", *p);
|
||||
else
|
||||
upsdebugx(4, "mge_command: sending [%02X]", *p);
|
||||
|
||||
if (write(upsfd, p, 1) != 1)
|
||||
return -1;
|
||||
|
||||
bytes_sent++;
|
||||
usleep(MGE_CHAR_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
if ( !reply )
|
||||
return bytes_rcvd;
|
||||
else
|
||||
usleep(MGE_REPLY_DELAY);
|
||||
|
||||
bytes_rcvd = ser_get_line(upsfd, reply, replylen,
|
||||
MGE_REPLY_ENDCHAR, MGE_REPLY_IGNCHAR, 3, 0);
|
||||
|
||||
upsdebugx(4, "mge_command: received %d byte(s)", bytes_rcvd);
|
||||
|
||||
return bytes_rcvd;
|
||||
}
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
upsdebugx(1, "cleaning up");
|
||||
disable_ups_comm();
|
||||
ser_close(upsfd, device_path);
|
||||
}
|
||||
235
drivers/mge-utalk.h
Normal file
235
drivers/mge-utalk.h
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
/* mge-utalk.h - monitor MGE UPS for NUT with UTalk protocol
|
||||
*
|
||||
* Copyright (C) 2002 - 2005
|
||||
* Arnaud Quette <arnaud.quette@free.fr> & <arnaud.quette@mgeups.com>
|
||||
* Hans Ekkehard Plesser <hans.plesser@itf.nlh.no>
|
||||
* Martin Loyer <martin@ouifi.net>
|
||||
* Patrick Agrain <patrick.agrain@alcatel.fr>
|
||||
* Nicholas Reilly <nreilly@magma.ca>
|
||||
* Dave Abbott <d.abbott@dcs.shef.ac.uk>
|
||||
* Marek Kralewski <marek@mercy49.de>
|
||||
*
|
||||
* This driver is a collaborative effort by the above people,
|
||||
* Sponsored by MGE UPS SYSTEMS <http://opensource.mgeups.com/>
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* Default Values for UPS Variables */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
#define DEFAULT_LOWBATT 30 /* low battery level, in % */
|
||||
|
||||
/* delay between return of utility power and powering up of load (in MINUTES) */
|
||||
#define DEFAULT_ONDELAY 1
|
||||
#define DEFAULT_OFFDELAY 20 /* delay before power off, in SECONDS */
|
||||
|
||||
#define MIN_CONFIRM_TIME 3 /* shutdown must be confirmed in */
|
||||
#define MAX_CONFIRM_TIME 15 /* this interval */
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* Model Name formating entries */
|
||||
/* --------------------------------------------------------------- */
|
||||
typedef struct {
|
||||
char *basename; /* as returned by Si 1 <data 2> */
|
||||
char *finalname;
|
||||
} models_name_t;
|
||||
|
||||
models_name_t Si1_models_names [] =
|
||||
{
|
||||
/* Pulsar EX */
|
||||
{ "Pulsar EX7", "Pulsar EX 7" },
|
||||
{ "Pulsar EX10", "Pulsar EX 10" },
|
||||
{ "Pulsar EX15", "Pulsar EX 15" },
|
||||
{ "Pulsar EX20", "Pulsar EX 20" },
|
||||
{ "Pulsar EX30", "Pulsar EX 30" },
|
||||
{ "Pulsar EX40", "Pulsar EX 40" },
|
||||
|
||||
/* Pulsar ES+ */
|
||||
{ "Pulsar ES2+", "Pulsar ES 2+" },
|
||||
{ "Pulsar ES5+", "Pulsar ES 5+" },
|
||||
|
||||
/* Pulsar ESV+ */
|
||||
{ "Pulsar ESV5+", "Pulsar ESV 5+" },
|
||||
{ "Pulsar ESV8+", "Pulsar ESV 8+" },
|
||||
{ "Pulsar ESV11+", "Pulsar ESV 11+" },
|
||||
{ "Pulsar ESV14+", "Pulsar ESV 14+" },
|
||||
{ "Pulsar ESV22+", "Pulsar ESV 22+" },
|
||||
|
||||
/* Pulsar EXtreme */
|
||||
{ "EXTREME 1500", "Pulsar EXtreme 1500" },
|
||||
{ "EXTREME 2000", "Pulsar EXtreme 2000" },
|
||||
{ "EXTREME 2500", "Pulsar EXtreme 2500" },
|
||||
{ "EXTREME 3000", "Pulsar EXtreme 3000" },
|
||||
|
||||
/* Comet EXtreme */
|
||||
{ "EXTREME 4.5", "Comet EXtreme 4.5" },
|
||||
{ "EXTREME 6", "Comet EXtreme 6" },
|
||||
{ "EXTREME 9", "Comet EXtreme 9" },
|
||||
{ "EXTREME 12", "Comet EXtreme 12" },
|
||||
|
||||
/* Comet */
|
||||
{ "COMET 5", "Comet 5" },
|
||||
{ "COMET 7", "Comet 7.5" },
|
||||
{ "COMET 10", "Comet 10" },
|
||||
{ "COMET 15", "Comet 15" },
|
||||
{ "COMET 20", "Comet 20" },
|
||||
{ "COMET 30", "Comet 30" },
|
||||
{ "COMET 12", "Comet 12" },
|
||||
{ "COMET 18", "Comet 18" },
|
||||
{ "COMET 24", "Comet 24" },
|
||||
{ "COMET 36", "Comet 36" },
|
||||
|
||||
/* FIXME: complete with Pulsar ?EL 2/4/7?, EXL, SX, PSX/CSX, Evolution?,... */
|
||||
|
||||
/* end of structure. */
|
||||
{ NULL, "Generic UTalk model" }
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* Model Information for legacy models */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
/* Structure defining how to get name and model for a particular Si output
|
||||
* This is for older UPS (Pulsar ESV,SV) which don't support Plug'n'Play 'Si 1' command
|
||||
*/
|
||||
typedef struct {
|
||||
int Data1; /* Data1, Family model */
|
||||
int Data2; /* Data2, Type */
|
||||
/* Data3, SoftLevel is not implemented here, while it's always null or zero.*/
|
||||
const char *name; /* ASCII model name (like 'Si 1' output */
|
||||
} mge_model_info_t;
|
||||
|
||||
/* Array containing Model information for legacy models
|
||||
* NOTE:
|
||||
* - Array is terminated by element with type NULL.
|
||||
*/
|
||||
|
||||
static mge_model_info_t mge_model[] = {
|
||||
/* Pulsar SV page */
|
||||
{ 3000, 5, "Pulsar SV3" },
|
||||
{ 3000, 6, "Pulsar SV5/8/11" },
|
||||
{ 3000, 7, "Pulsar SV3" },
|
||||
{ 3000, 8, "Pulsar SV5/8" },
|
||||
{ 3000, 10, "Pulsar SV11/ESV13" },
|
||||
/* Pulsar ESV page */
|
||||
{ 3000, 11, "Pulsar ESV17" },
|
||||
{ 3000, 12, "Pulsar ESV20" },
|
||||
{ 3000, 13, "Pulsar ESV13" },
|
||||
{ 3000, 14, "Pulsar ESV17" },
|
||||
{ 3000, 15, "Pulsar ESV20" },
|
||||
{ 3000, 17, "Pulsar ESV8" },
|
||||
/* Pulsar ESV+ compatibility (though these support Si1) */
|
||||
{ 3000, 51, "Pulsar ESV 11+" },
|
||||
{ 0, 0, NULL }
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* Multiplier Tables */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
/* First index : Table number, fetched with "Ai" command
|
||||
* Second index: unit, as in enum above
|
||||
* NOTE:
|
||||
* - to make the table index the same as the MGE table number,
|
||||
* dummy table row multiplier[0][] is inserted
|
||||
* - unit MIN2SEC is used to convert values in minutes sent by
|
||||
* the UPS (WAKEDELAY) to seconds
|
||||
* - the final column corresponds to unit NONE
|
||||
*/
|
||||
|
||||
/* units in multiplier table */
|
||||
typedef enum eunits
|
||||
{ VOLT = 0, AMPERE, HERTZ, VOLTAMP, WATT, DEGCELS, MIN2SEC, NONE } units_t;
|
||||
|
||||
static const double multiplier[4][8] = {
|
||||
/* V A Hz VA W C MIN2SEC NONE */
|
||||
{ 1 , 1 , 1 , 1, 1, 1, 60, 1 },
|
||||
{ 1 , 1 , 0.1 , 1000, 1000, 1, 60, 1 },
|
||||
{ 0.01, 0.01, 1 , 1, 1, 1, 60, 1 },
|
||||
{ 1 , 0.01, 0.1 , 1, 1, 1, 60, 1 }
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* Explicit Booleans */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
/* use explicit booleans */
|
||||
#ifdef FALSE
|
||||
#undef FALSE
|
||||
#endif /* FALSE */
|
||||
#ifdef TRUE
|
||||
#undef TRUE
|
||||
#endif /* TRUE */
|
||||
typedef enum ebool { FALSE=0, TRUE } bool_t;
|
||||
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* Query Commands and their Mapping to INFO_ Variables */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
/* Structure defining how to query UPS for a variable and write
|
||||
information to INFO structure.
|
||||
*/
|
||||
typedef struct {
|
||||
const char *type; /* INFO_* element */
|
||||
int flags; /* INFO-element flags to set in addinfo */
|
||||
int length; /* INFO-element length of strings */
|
||||
const char cmd[32]; /* UPS command string to requets element */
|
||||
const char fmt[32]; /* printf format string for INFO entry */
|
||||
units_t unit; /* unit of measurement, or NONE */
|
||||
bool_t ok; /* flag indicating if item is available */
|
||||
} mge_info_item_t;
|
||||
|
||||
/* Array containing information to translate between UTalk and NUT info
|
||||
* NOTE:
|
||||
* - Array is terminated by element with type NULL.
|
||||
* - Essential INFO items (_MFR, _MODEL, _FIRMWARE, _STATUS) are
|
||||
* handled separately.
|
||||
* - Array is NOT const, since "ok" can be changed.
|
||||
*/
|
||||
|
||||
static mge_info_item_t mge_info[] = {
|
||||
/* Battery page */
|
||||
{ "battery.charge", 0, 0, "Bl", "%05.1f", NONE, TRUE },
|
||||
{ "battery.runtime", 0, 0, "Bn", "%05d", NONE, TRUE },
|
||||
{ "battery.voltage", 0, 0, "Bv", "%05.1f", VOLT, TRUE },
|
||||
{ "battery.charge.low", ST_FLAG_RW | ST_FLAG_STRING, 2, "Bl ?", "%02d", NONE, TRUE },
|
||||
{ "battery.voltage.nominal", 0, 0, "Bv ?", "%05.1f", VOLT, TRUE },
|
||||
/* UPS page */
|
||||
{ "ups.temperature", 0, 0, "St", "%05.1f", DEGCELS, TRUE },
|
||||
{ "ups.load", 0, 0, "Ll", "%05.1f", NONE, TRUE },
|
||||
{ "ups.delay.start", ST_FLAG_RW | ST_FLAG_STRING, 5, "Sm ?", "%03d", NONE, TRUE },
|
||||
{ "ups.delay.shutdown", ST_FLAG_RW | ST_FLAG_STRING, 5, "Sn ?", "%03d", NONE, TRUE },
|
||||
{ "ups.test.interval", ST_FLAG_RW | ST_FLAG_STRING, 5, "Bp ?", "%03d", NONE, TRUE },
|
||||
/* Output page */
|
||||
{ "output.voltage", 0, 0, "Lv", "%05.1f", VOLT, TRUE },
|
||||
{ "output.current", 0, 0, "Lc", "%05.1f", AMPERE, TRUE },
|
||||
/* Input page */
|
||||
{ "input.voltage", 0, 0, "Uv", "%05.1f", VOLT, TRUE },
|
||||
{ "input.frequency", 0, 0, "Uf", "%05.2f", HERTZ, TRUE },
|
||||
/* same as LOBOOSTXFER */
|
||||
{ "input.transfer.low", ST_FLAG_RW | ST_FLAG_STRING, 5, "Ee ?", "%05.1f", VOLT, TRUE },
|
||||
{ "input.transfer.boost.low", ST_FLAG_RW | ST_FLAG_STRING, 5, "Ee ?", "%05.1f", VOLT, TRUE },
|
||||
{ "input.transfer.boost.high", ST_FLAG_RW | ST_FLAG_STRING, 5, "Eo ?", "%05.1f", VOLT, TRUE },
|
||||
{ "input.transfer.trim.low", ST_FLAG_RW | ST_FLAG_STRING, 5, "Ea ?", "%05.1f", VOLT, TRUE },
|
||||
/* same as HITRIMXFER */
|
||||
{ "input.transfer.high", ST_FLAG_RW | ST_FLAG_STRING, 5, "Eu ?", "%05.1f", VOLT, TRUE },
|
||||
{ "input.transfer.trim.high", ST_FLAG_RW | ST_FLAG_STRING, 5, "Eu ?", "%05.1f", VOLT, TRUE },
|
||||
/* terminating element */
|
||||
{ NULL, 0, 0, "\0", "\0", NONE, FALSE }
|
||||
};
|
||||
1118
drivers/mge-xml.c
Normal file
1118
drivers/mge-xml.c
Normal file
File diff suppressed because it is too large
Load diff
28
drivers/mge-xml.h
Normal file
28
drivers/mge-xml.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/* mge-xml.h Model specific data for MGE XML protocol UPSes
|
||||
|
||||
Copyright (C)
|
||||
2008 Arjen de Korte <adkorte-guest@alioth.debian.org>
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
#ifndef MGE_XML_H
|
||||
#define MGE_XML_H
|
||||
|
||||
#include "netxml-ups.h"
|
||||
|
||||
extern subdriver_t mge_xml_subdriver;
|
||||
|
||||
#endif /* MGE_XML_H */
|
||||
982
drivers/microdowell.c
Normal file
982
drivers/microdowell.c
Normal file
|
|
@ -0,0 +1,982 @@
|
|||
/*
|
||||
*
|
||||
* microdowell.c: support for Microdowell Enterprise Nxx/Bxx serial protocol based UPSes
|
||||
*
|
||||
* Copyright (C) Elio Corbolante <eliocor at microdowell.com>
|
||||
*
|
||||
* microdowell.c created on 27/09/2007
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
/*
|
||||
anything commented is optional
|
||||
anything else is mandatory
|
||||
*/
|
||||
|
||||
#define ENTERPRISE_PROTOCOL
|
||||
|
||||
#include "microdowell.h"
|
||||
|
||||
#include "main.h"
|
||||
#include "serial.h"
|
||||
#include <sys/ioctl.h>
|
||||
#include "timehead.h"
|
||||
|
||||
|
||||
#define MAX_START_DELAY 999999
|
||||
#define MAX_SHUTDOWN_DELAY 32767
|
||||
/* Maximum length of a string representing these values */
|
||||
#define MAX_START_DELAY_LEN 6
|
||||
#define MAX_SHUTDOWN_DELAY_LEN 5
|
||||
|
||||
#define DRIVER_NAME "MICRODOWELL UPS driver"
|
||||
#define DRIVER_VERSION "0.01"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Elio Corbolante <eliocor@microdowell.com>",
|
||||
DRV_STABLE,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
ENT_STRUCT ups ;
|
||||
int instcmd(const char *cmdname, const char *extra);
|
||||
int setvar(const char *varname, const char *val);
|
||||
|
||||
/* he knew... macros should evaluate their arguments only once */
|
||||
#define CLAMP(x, min, max) (((x) < (min)) ? (min) : (((x) > (max)) ? (max) : (x)))
|
||||
|
||||
static int CheckDataChecksum(unsigned char *Buff, int Len)
|
||||
{
|
||||
int i, Idx ;
|
||||
unsigned char Xor ;
|
||||
|
||||
ups.FramePointer = Xor = 0 ;
|
||||
for (Idx=0 ; Idx < Len ; Idx++)
|
||||
if (Buff[Idx] == STX_CHAR)
|
||||
break ;
|
||||
|
||||
ups.FramePointer = Idx ; /* Memorise start point. */
|
||||
|
||||
/* Check that the message is not to short... */
|
||||
if ( (Idx > (Len-4)) || (Idx+Buff[Idx+1]+2 > Len) )
|
||||
return(ERR_MSG_TOO_SHORT) ; /* To short message! */
|
||||
|
||||
/* Calculate checksum */
|
||||
for (i=Idx+1 ; i < Idx+Buff[Idx+1]+2 ; i++)
|
||||
Xor ^= Buff[i] ;
|
||||
|
||||
/* if Xor != then checksum error */
|
||||
if (Xor != Buff[i])
|
||||
return(ERR_MSG_CHECKSUM) ; /* error in checksum */
|
||||
|
||||
/* If checksum OK: return */
|
||||
return(0) ;
|
||||
}
|
||||
|
||||
|
||||
static char *ErrMessages[] = {
|
||||
/* 0 */ "errorcode NOT DEFINED", /* default error message */
|
||||
/* 1 */ "I2C bus busy (e2prom)",
|
||||
/* 2 */ "Command received: checksum not valid",
|
||||
/* 3 */ "Command received: unrecognized command",
|
||||
/* 4 */ "WRITE: eeprom address not multiple of 8",
|
||||
/* 5 */ "READ: eeprom address (added with size) out of bound ",
|
||||
/* 6 */ "error writing e2prom address",
|
||||
/* 7 */ "error writing e2prom subaddress",
|
||||
/* 8 */ "error reading e2prom data",
|
||||
/* 9 */ "error writing e2prom address",
|
||||
/* 10 */ "error reading e2prom subaddress",
|
||||
/* 11 */ "error writing e2prom data",
|
||||
/* 12 */ "error writing e2prom address during data verification",
|
||||
/* 13 */ "error verification e2prom data",
|
||||
/* 14 */ "e2prom data are different from those in the write buffer",
|
||||
/* 15 */ "e2prom checksum error",
|
||||
|
||||
/* 16 */ "NO CHARS FROM PORT",
|
||||
/* 17 */ "TOO FEW DATA RECEIVED: [STX] near end of message",
|
||||
/* 18 */ "CHECKSUM ERROR IN MESSAGE",
|
||||
/* 19 */ "OK",
|
||||
/* */ ""
|
||||
} ;
|
||||
|
||||
const char *PrintErr(int ErrCode)
|
||||
{
|
||||
int msgIndex = 0 ;
|
||||
|
||||
/* The default 'msgIndex' is 0 (error code not defined) */
|
||||
switch (ErrCode) {
|
||||
case ERR_NO_ERROR : msgIndex = 19 ; break ;
|
||||
|
||||
case ERR_I2C_BUSY : msgIndex = 1 ; break ;
|
||||
case ERR_CMD_CHECKSUM : msgIndex = 2 ; break ;
|
||||
case ERR_CMD_UNRECOG : msgIndex = 3 ; break ;
|
||||
case ERR_EEP_NOBLOCK : msgIndex = 4 ; break ;
|
||||
case ERR_EEP_OOBOUND : msgIndex = 5 ; break ;
|
||||
case ERR_EEP_WADDR1 : msgIndex = 6 ; break ;
|
||||
case ERR_EEP_WSADDR1 : msgIndex = 7 ; break ;
|
||||
case ERR_EEP_RDATA : msgIndex = 8 ; break ;
|
||||
case ERR_EEP_WADDR2 : msgIndex = 9 ; break ;
|
||||
case ERR_EEP_WSADDR2 : msgIndex = 10 ; break ;
|
||||
case ERR_EEP_WDATA : msgIndex = 11 ; break ;
|
||||
case ERR_EEP_WADDRVER : msgIndex = 12 ; break ;
|
||||
case ERR_EEP_WDATAVER : msgIndex = 13 ; break ;
|
||||
case ERR_EEP_VERIFY : msgIndex = 14 ; break ;
|
||||
case ERR_EEP_CHECKSUM : msgIndex = 15 ; break ;
|
||||
|
||||
case ERR_COM_NO_CHARS : msgIndex = 16 ; break ;
|
||||
case ERR_MSG_TOO_SHORT : msgIndex = 17 ; break ;
|
||||
case ERR_MSG_CHECKSUM : msgIndex = 18 ; break ;
|
||||
default: msgIndex = 0 ; break ;
|
||||
}
|
||||
return(ErrMessages[msgIndex]) ;
|
||||
}
|
||||
|
||||
|
||||
int CheckErrCode(unsigned char * Buff)
|
||||
{
|
||||
auto int Ret ;
|
||||
|
||||
switch (Buff[2]) {
|
||||
/* I have found an error */
|
||||
case CMD_NACK :
|
||||
Ret = Buff[3] ;
|
||||
break ;
|
||||
|
||||
case CMD_ACK :
|
||||
case CMD_GET_STATUS :
|
||||
case CMD_GET_MEASURES :
|
||||
case CMD_GET_CONFIG :
|
||||
case CMD_GET_BATT_STAT :
|
||||
case CMD_GET_MASK :
|
||||
case CMD_SET_TIMER :
|
||||
case CMD_BATT_TEST :
|
||||
case CMD_GET_BATT_TEST :
|
||||
case CMD_SD_ONESHOT :
|
||||
case CMD_GET_SD_ONESHOT:
|
||||
case CMD_SET_SCHEDULE :
|
||||
case CMD_GET_SCHEDULE :
|
||||
case CMD_GET_EEP_BLOCK :
|
||||
case CMD_SET_EEP_BLOCK :
|
||||
case CMD_GET_EEP_SEED :
|
||||
case CMD_INIT :
|
||||
Ret = 0 ;
|
||||
break ;
|
||||
|
||||
/* command not recognized */
|
||||
default:
|
||||
Ret = ERR_CMD_UNRECOG ;
|
||||
break ;
|
||||
}
|
||||
return(Ret) ;
|
||||
}
|
||||
|
||||
|
||||
void SendCmdToSerial(unsigned char *Buff, int Len)
|
||||
{
|
||||
int i, ret ;
|
||||
unsigned char Tmp[20], Xor ;
|
||||
|
||||
Tmp[0] = STX_CHAR ;
|
||||
Xor = Tmp[1] = (unsigned char) (Len & 0x1f) ;
|
||||
for (i=0 ; i < Tmp[1] ; i++)
|
||||
{
|
||||
Tmp[i+2] = Buff[i] ;
|
||||
Xor ^= Buff[i] ;
|
||||
}
|
||||
Tmp[Len+2] = Xor ;
|
||||
|
||||
upsdebug_hex(4, "->UPS", Tmp, Len+3) ;
|
||||
|
||||
/* flush serial port */
|
||||
ret = ser_flush_in(upsfd, "", 0) ; /* empty input buffer */
|
||||
|
||||
ret = ser_send_buf(upsfd, Tmp, Len+3) ; /* send data to the UPS */
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
unsigned char * CmdSerial(unsigned char *OutBuffer, int Len, unsigned char *RetBuffer)
|
||||
{
|
||||
#define TMP_BUFF_LEN 1024
|
||||
unsigned char InpBuff[TMP_BUFF_LEN+1] ;
|
||||
unsigned char TmpBuff[3] ;
|
||||
int i, ErrCode ;
|
||||
unsigned char *p ;
|
||||
int BuffLen ;
|
||||
|
||||
// The default error code (no received character)
|
||||
ErrCode = ERR_COM_NO_CHARS ;
|
||||
|
||||
SendCmdToSerial(OutBuffer, Len) ;
|
||||
usleep(10000) ; // small delay (1/100 s))
|
||||
|
||||
// get chars until timeout
|
||||
BuffLen = 0 ;
|
||||
while (ser_get_char(upsfd, TmpBuff, 0, 10000) == 1)
|
||||
{
|
||||
InpBuff[BuffLen++] = TmpBuff[0] ;
|
||||
if (BuffLen > TMP_BUFF_LEN)
|
||||
break ;
|
||||
}
|
||||
|
||||
upsdebug_hex(4, "UPS->", InpBuff, BuffLen) ;
|
||||
|
||||
if (BuffLen > 0)
|
||||
{
|
||||
ErrCode = CheckDataChecksum(InpBuff, BuffLen) ;
|
||||
/* upsdebugx(4, "ErrCode = %d / Len = %d", ErrCode, BuffLen); */
|
||||
|
||||
if (!ErrCode)
|
||||
{
|
||||
/* FramePointer to valid data! */
|
||||
p = InpBuff + ups.FramePointer ;
|
||||
/* p now point to valid data.
|
||||
check if it is a error code. */
|
||||
ErrCode = CheckErrCode(p) ;
|
||||
if (!ErrCode)
|
||||
{
|
||||
/* I copy the data read in the buffer */
|
||||
for(i=0 ; i<(int) (p[1])+3 ; i++)
|
||||
RetBuffer[i] = p[i] ;
|
||||
ups.ErrCode = ups.ErrCount = ups.CommStatus = 0 ;
|
||||
return(RetBuffer) ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* if they have arrived here, wants to say that I have found an error.... */
|
||||
ups.ErrCode = ErrCode ;
|
||||
ups.ErrCount++ ;
|
||||
if (ups.ErrCount > 3)
|
||||
{
|
||||
ups.CommStatus &= 0x80 ;
|
||||
ups.CommStatus |= (unsigned char) (ups.ErrCount & 0x7F) ;
|
||||
if (ups.ErrCount > 100)
|
||||
ups.ErrCount = 100 ;
|
||||
}
|
||||
return(NULL) ; /* There have been errors in the reading of the data */
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int detect_hardware(void)
|
||||
{
|
||||
unsigned char OutBuff[20] ;
|
||||
unsigned char InpBuff[260] ;
|
||||
unsigned char *p ;
|
||||
int i, retries ;
|
||||
struct tm *Time ;
|
||||
time_t lTime ;
|
||||
|
||||
ups.ge_2kVA = 0 ;
|
||||
|
||||
for (retries=0 ; retries <= 4 ; retries++)
|
||||
{
|
||||
/* Identify UPS model */
|
||||
OutBuff[0] = CMD_GET_EEP_BLOCK ; /* get EEPROM data */
|
||||
OutBuff[1] = EEP_UPS_MODEL ; /* UPS model */
|
||||
OutBuff[2] = 8 ; /* number of bytes */
|
||||
if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
|
||||
{
|
||||
/* got UPS model */
|
||||
for (i=0 ; i<8 ; i++)
|
||||
ups.UpsModel[i] = p[i+5] ;
|
||||
ups.UpsModel[8] = '\0' ;
|
||||
upsdebugx(2, "get 'UPS model': %s", PrintErr(ups.ErrCode));
|
||||
break ; /* UPS identified: exit from ' for' LOOP */
|
||||
}
|
||||
else
|
||||
{
|
||||
upsdebugx(1, "[%d] get 'UPS model': %s", retries, PrintErr(ups.ErrCode));
|
||||
upslogx(LOG_ERR, "[%d] Unable to identify UPS model [%s]", retries, PrintErr(ups.ErrCode));
|
||||
usleep(100000) ; /* small delay (1/10 s) for next retry */
|
||||
}
|
||||
}
|
||||
|
||||
/* check if I was unable to find the UPS */
|
||||
if (retries == 4) /* UPS not found! */
|
||||
return -1;
|
||||
|
||||
/* UPS serial number */
|
||||
OutBuff[0] = CMD_GET_EEP_BLOCK ; /* get EEPROM data */
|
||||
OutBuff[1] = EEP_SERIAL_NUM ; /* UPS serial # */
|
||||
OutBuff[2] = 8 ; /* number of bytes */
|
||||
if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
|
||||
{
|
||||
/* got UPS serial # */
|
||||
for (i=0 ; i<8 ; i++)
|
||||
ups.SerialNumber[i] = p[i+5] ;
|
||||
ups.SerialNumber[8] = '\0' ;
|
||||
upsdebugx(2, "get 'UPS Serial #': %s", PrintErr(ups.ErrCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
upsdebugx(1, "get 'UPS Serial #': %s", PrintErr(ups.ErrCode));
|
||||
upslogx(LOG_ERR, "Unable to identify UPS serial # [%s]", PrintErr(ups.ErrCode));
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/* Get Production date & FW info */
|
||||
OutBuff[0] = CMD_GET_EEP_BLOCK ; /* get EEPROM data */
|
||||
OutBuff[1] = EEP_PROD_DATE ; /* Production date + HW version */
|
||||
OutBuff[2] = 8 ; /* number of bytes */
|
||||
if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
|
||||
{
|
||||
/* got Production date & FW info */
|
||||
p += 5 ; /* 'p' points to eeprom data */
|
||||
ups.YearOfProd = 2000 + p[0] ; /* Production year of the UPS */
|
||||
ups.MonthOfProd = p[1] ; /* Production month of the UPS */
|
||||
ups.DayOfProd = p[2] ; /* Production day of the UPS */
|
||||
ups.HW_MajorVersion = (p[3]>>4) & 0x0F ; /* Hardware: Major version */
|
||||
ups.HW_MinorVersion = (p[3] & 0x0F) ; /* Hardware: Minor version */
|
||||
ups.BR_MajorVersion = (p[4]>>4) & 0x0F ; /* BoardHardware: Major version */
|
||||
ups.BR_MinorVersion = (p[4] & 0x0F) ; /* BoardHardware: Minor version */
|
||||
ups.FW_MajorVersion = (p[5]>>4) & 0x0F ; /* Firmware: Major version */
|
||||
ups.FW_MinorVersion = (p[5] & 0x0F) ; /* Firmware: Minor version */
|
||||
ups.FW_SubVersion = p[6] ; /* Firmware: SUBVERSION (special releases */
|
||||
ups.BatteryNumber = p[7] ; /* number of batteries in UPS */
|
||||
upsdebugx(2, "get 'Production date': %s", PrintErr(ups.ErrCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
upsdebugx(1, "get 'Production date': %s", PrintErr(ups.ErrCode));
|
||||
upslogx(LOG_ERR, "Unable to read Production date [%s]", PrintErr(ups.ErrCode));
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/* Get Battery substitution date */
|
||||
OutBuff[0] = CMD_GET_EEP_BLOCK ; /* get EEPROM data */
|
||||
OutBuff[1] = EEP_BATT_SUBST ; /* Battery substitution dates */
|
||||
OutBuff[2] = 8 ; /* number of bytes */
|
||||
if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
|
||||
{
|
||||
/* got Battery substitution date */
|
||||
p += 5 ; /* 'p' points to eeprom data */
|
||||
upsdebugx(2, "get 'Battery Subst. Dates': %s", PrintErr(ups.ErrCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
upsdebugx(1, "get 'Battery Subst. Dates': %s", PrintErr(ups.ErrCode));
|
||||
upslogx(LOG_ERR, "Unable to read Battery Subst. Dates [%s]", PrintErr(ups.ErrCode));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Get working time (battery+normal)) */
|
||||
OutBuff[0] = CMD_GET_EEP_BLOCK ; /* get EEPROM data */
|
||||
OutBuff[1] = EEP_MIN_VBATT ; /* working time */
|
||||
OutBuff[2] = 8 ; /* number of bytes */
|
||||
if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
|
||||
{
|
||||
/* got working time (battery+normal)) */
|
||||
p += 5 ; /* 'p' points to eeprom data */
|
||||
upsdebugx(2, "get 'UPS life info': %s", PrintErr(ups.ErrCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
upsdebugx(1, "get 'UPS life info': %s", PrintErr(ups.ErrCode));
|
||||
upslogx(LOG_ERR, "Unable to read UPS life info [%s]", PrintErr(ups.ErrCode));
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/* Get the THRESHOLD table (0) */
|
||||
OutBuff[0] = CMD_GET_EEP_BLOCK ; /* get EEPROM data */
|
||||
OutBuff[1] = EEP_THRESHOLD_0 ; /* Thresholds table 0 */
|
||||
OutBuff[2] = 8 ; /* number of bytes */
|
||||
if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
|
||||
{
|
||||
/* got the THRESHOLD table (0) */
|
||||
p += 5 ; /* 'p' points to eeprom data */
|
||||
upsdebugx(2, "get 'Thresholds table 0': %s", PrintErr(ups.ErrCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
upsdebugx(1, "get 'Thresholds table 0': %s", PrintErr(ups.ErrCode));
|
||||
upslogx(LOG_ERR, "Unable to read Thresholds table 0 [%s]", PrintErr(ups.ErrCode));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Get the THRESHOLD table (1) */
|
||||
OutBuff[0] = CMD_GET_EEP_BLOCK ; /* get EEPROM data */
|
||||
OutBuff[1] = EEP_THRESHOLD_1 ; /* Thresholds table 0 */
|
||||
OutBuff[2] = 8 ; /* number of bytes */
|
||||
if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
|
||||
{
|
||||
/* got the THRESHOLD table (1) */
|
||||
p += 5 ; /* 'p' points to eeprom data */
|
||||
upsdebugx(2, "get 'Thresholds table 1': %s", PrintErr(ups.ErrCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
upsdebugx(1, "get 'Thresholds table 1': %s", PrintErr(ups.ErrCode));
|
||||
upslogx(LOG_ERR, "Unable to read Thresholds table 1 [%s]", PrintErr(ups.ErrCode));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Get the THRESHOLD table (2) */
|
||||
OutBuff[0] = CMD_GET_EEP_BLOCK ; /* get EEPROM data */
|
||||
OutBuff[1] = EEP_THRESHOLD_2 ; /* Thresholds table 0 */
|
||||
OutBuff[2] = 8 ; /* number of bytes */
|
||||
if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
|
||||
{
|
||||
/* got the THRESHOLD table (2) */
|
||||
p += 5 ; /* 'p' points to eeprom data */
|
||||
upsdebugx(2, "get 'Thresholds table 2': %s", PrintErr(ups.ErrCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
upsdebugx(1, "get 'Thresholds table 2': %s", PrintErr(ups.ErrCode));
|
||||
upslogx(LOG_ERR, "Unable to read Thresholds table 2 [%s]", PrintErr(ups.ErrCode));
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/* Get Option Bytes */
|
||||
OutBuff[0] = CMD_GET_EEP_BLOCK ; /* get EEPROM data */
|
||||
OutBuff[1] = EEP_OPT_BYTE_BLK ; /* Option Bytes */
|
||||
OutBuff[2] = 8 ; /* number of bytes */
|
||||
if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
|
||||
{
|
||||
/* got Option Bytes */
|
||||
p += 5 ; /* 'p' points to eeprom data */
|
||||
dstate_setinfo("input.voltage.nominal", "%s", (p[EEP_OPT_BYTE_1] & 0x02) ? "110": "230") ;
|
||||
dstate_setinfo("input.frequency", "%s", (p[EEP_OPT_BYTE_1] & 0x01) ? "60.0": "50.0") ;
|
||||
upsdebugx(2, "get 'Option Bytes': %s", PrintErr(ups.ErrCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
upsdebugx(1, "get 'Option Bytes': %s", PrintErr(ups.ErrCode));
|
||||
upslogx(LOG_ERR, "Unable to read Option Bytes [%s]", PrintErr(ups.ErrCode));
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Get UPS sensitivity (fault points) */
|
||||
OutBuff[0] = CMD_GET_EEP_BLOCK ; /* get EEPROM data */
|
||||
OutBuff[1] = EEP_FAULT_POINTS ; /* Number of fault points (sensitivity)) */
|
||||
OutBuff[2] = 8 ; /* number of bytes */
|
||||
if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
|
||||
{
|
||||
/* got UPS sensitivity (fault points) */
|
||||
p += 5 ; /* 'p' points to eeprom data */
|
||||
switch (p[0]) {
|
||||
case 1 : dstate_setinfo("input.sensitivity", "H") ; break ;
|
||||
case 2 : dstate_setinfo("input.sensitivity", "M") ; break ;
|
||||
case 3 : dstate_setinfo("input.sensitivity", "L") ; break ;
|
||||
default : dstate_setinfo("input.sensitivity", "L") ; break ;
|
||||
}
|
||||
upsdebugx(2, "get 'Input Sensitivity': %s", PrintErr(ups.ErrCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
upsdebugx(1, "get 'Input Sensitivity': %s", PrintErr(ups.ErrCode));
|
||||
upslogx(LOG_ERR, "Unable to read Input Sensitivity [%s]", PrintErr(ups.ErrCode));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set internal UPS clock */
|
||||
time(&lTime) ;
|
||||
Time = localtime(&lTime) ;
|
||||
|
||||
OutBuff[0] = CMD_SET_TIMER ; /* set UPS internal timer */
|
||||
OutBuff[1] = (Time->tm_wday+6) % 7 ; /* week day (0=monday) */
|
||||
OutBuff[2] = Time->tm_hour ; /* hours */
|
||||
OutBuff[3] = Time->tm_min ; /* minutes */
|
||||
OutBuff[4] = Time->tm_sec; /* seconds */
|
||||
if ((p = CmdSerial(OutBuff, LEN_SET_TIMER, InpBuff)) != NULL)
|
||||
{
|
||||
upsdebugx(2, "set 'UPS internal clock': %s", PrintErr(ups.ErrCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
upsdebugx(1, "set 'UPS internal clock': %s", PrintErr(ups.ErrCode));
|
||||
upslogx(LOG_ERR, "Unable to set UPS internal clock [%s]", PrintErr(ups.ErrCode));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0; /* everything was OK */
|
||||
}
|
||||
|
||||
|
||||
/* ========================= */
|
||||
|
||||
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
unsigned char OutBuff[20] ;
|
||||
unsigned char InpBuff[260] ;
|
||||
unsigned char *p ;
|
||||
/* int i ; */
|
||||
|
||||
OutBuff[0] = CMD_GET_STATUS ; /* get UPS status */
|
||||
if ((p = CmdSerial(OutBuff, LEN_GET_STATUS, InpBuff)) != NULL)
|
||||
{
|
||||
p += 3 ; /* 'p' points to received data */
|
||||
|
||||
status_init(); /* reset status flags */
|
||||
|
||||
/* store last UPS status */
|
||||
ups.StatusUPS = (int)p[0] | ((int)p[1]<<8) | ((int)p[2]<<16) | ((int)p[3]<<24) ;
|
||||
ups.ShortStatus = (int)p[0] | ((int)p[1]<<8) ;
|
||||
upsdebugx(1, "ups.StatusUPS: %08lX", ups.StatusUPS);
|
||||
upsdebugx(1, "ups.ShortStatus: %04X", ups.ShortStatus);
|
||||
|
||||
/* on battery? */
|
||||
if (p[0] & 0x01)
|
||||
status_set("OB"); /* YES */
|
||||
|
||||
/* LOW battery? */
|
||||
if (p[0] & 0x02)
|
||||
status_set("LB"); /* YES */
|
||||
|
||||
/* online? */
|
||||
if (p[0] & 0x08)
|
||||
status_set("OL"); /* YES */
|
||||
|
||||
/* Overload? */
|
||||
if (p[1] & 0xC0)
|
||||
status_set("OVER"); /* YES */
|
||||
|
||||
/* Offline/Init/Stanby/Waiting for mains? */
|
||||
if (p[0] & 0xE0)
|
||||
status_set("OFF"); /* YES */
|
||||
|
||||
/* AVR on (boost)? */
|
||||
if (p[4] & 0x04)
|
||||
status_set("BOOST"); /* YES */
|
||||
|
||||
/* AVR on (buck)? */
|
||||
if (p[4] & 0x08)
|
||||
status_set("TRIM"); /* YES */
|
||||
|
||||
dstate_setinfo("ups.time", "%02d:%02d:%02d", p[6], p[7], p[8]) ;
|
||||
upsdebugx(3, "get 'Get Status': %s", PrintErr(ups.ErrCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
upsdebugx(1, "get 'Get Status': %s", PrintErr(ups.ErrCode));
|
||||
/* upslogx(LOG_ERR, "get 'Get Status': %s", PrintErr(ups.ErrCode)); */
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
|
||||
/* ========================= */
|
||||
|
||||
OutBuff[0] = CMD_GET_MEASURES ; /* get UPS values */
|
||||
if ((p = CmdSerial(OutBuff, LEN_GET_MEASURES, InpBuff)) != NULL)
|
||||
{
|
||||
p += 3 ; /* 'p' points to received data */
|
||||
|
||||
dstate_setinfo("input.voltage", "%d", (int)((float)(p[2]*256 + p[3]) / 36.4)) ;
|
||||
if (ups.ge_2kVA)
|
||||
{
|
||||
dstate_setinfo("output.voltage", "%d", (int)((float)(p[6]*256 + p[7]) / 63.8)) ;
|
||||
dstate_setinfo("output.current", "%1.f", ((float)(p[8]*256 + p[9]) / 635.0)) ;
|
||||
dstate_setinfo("battery.voltage", "%.1f", ((float) (p[4]*256 + p[5])) / 329.0) ;
|
||||
}
|
||||
else
|
||||
{
|
||||
dstate_setinfo("output.voltage", "%d", (int)((float)(p[6]*256 + p[7]) / 36.4)) ;
|
||||
dstate_setinfo("output.current", "%1.f", ((float)(p[8]*256 + p[9]) / 1350.0)) ;
|
||||
dstate_setinfo("battery.voltage", "%.1f", ((float) (p[4]*256 + p[5])) / 585.0) ;
|
||||
}
|
||||
|
||||
dstate_setinfo("ups.temperature", "%d", (int)(((float)(p[10]*256 + p[11])-202.97) / 1.424051)) ;
|
||||
upsdebugx(3, "get 'Get Measures': %s", PrintErr(ups.ErrCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
/* upsdebugx(1, "get 'Get Measures': %s", PrintErr(ups.ErrCode)); */
|
||||
upslogx(LOG_ERR, "get 'Get Measures': %s", PrintErr(ups.ErrCode));
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
|
||||
/* ========================= */
|
||||
|
||||
OutBuff[0] = CMD_GET_BAT_LD ; /* get UPS Battery and Load values */
|
||||
if ((p = CmdSerial(OutBuff, LEN_GET_BAT_LD, InpBuff)) != NULL)
|
||||
{
|
||||
p += 3 ; /* 'p' points to received data */
|
||||
|
||||
dstate_setinfo("ups.power", "%d", (p[4]*256 + p[5])) ;
|
||||
/* dstate_setinfo("ups.realpower", "%d", (int)((float)(p[4]*256 + p[5]) * 0.6)) ; */
|
||||
dstate_setinfo("battery.charge", "%d", (int)p[0]) ;
|
||||
dstate_setinfo("ups.load", "%d", (int)p[6]) ;
|
||||
upsdebugx(3, "get 'Get Batt+Load Status': %s", PrintErr(ups.ErrCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
/* upsdebugx(1, "get 'Get Batt+Load Status': %s", PrintErr(ups.ErrCode)); */
|
||||
upslogx(LOG_ERR, "get 'Get Batt+Load Status': %s", PrintErr(ups.ErrCode));
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
|
||||
status_commit();
|
||||
dstate_dataok();
|
||||
|
||||
poll_interval = 2;
|
||||
}
|
||||
|
||||
|
||||
/* ========================= */
|
||||
|
||||
|
||||
|
||||
|
||||
int instcmd(const char *cmdname, const char *extra)
|
||||
{
|
||||
unsigned char OutBuff[20] ;
|
||||
unsigned char InpBuff[260] ;
|
||||
unsigned char *p ;
|
||||
/* int i ; */
|
||||
|
||||
upsdebugx(1, "instcmd(%s, %s)", cmdname, extra);
|
||||
|
||||
|
||||
if (strcasecmp(cmdname, "load.on") == 0)
|
||||
{
|
||||
OutBuff[0] = CMD_SD_ONESHOT ; /* turn ON the outputs */
|
||||
OutBuff[1] = 0xFF ; /* ALL outputs */
|
||||
OutBuff[2] = 0x08 ; /* Enable outputs (immediately) */
|
||||
OutBuff[3] = 0 ;
|
||||
OutBuff[4] = 0 ;
|
||||
OutBuff[5] = 0 ;
|
||||
OutBuff[6] = 0 ;
|
||||
OutBuff[7] = 0 ;
|
||||
if ((p = CmdSerial(OutBuff, LEN_SD_ONESHOT, InpBuff)) != NULL)
|
||||
{
|
||||
p += 3 ; /* 'p' points to received data */
|
||||
upslogx(LOG_INFO, "Turning load on.");
|
||||
upsdebugx(1, "'SdOneshot(turn_ON)': %s", PrintErr(ups.ErrCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
upsdebugx(1, "'SdOneshot(turn_ON)': %s", PrintErr(ups.ErrCode));
|
||||
upslogx(LOG_ERR, "'SdOneshot(turn_ON)': %s", PrintErr(ups.ErrCode));
|
||||
}
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
if (strcasecmp(cmdname, "load.off") == 0)
|
||||
{
|
||||
OutBuff[0] = CMD_SD_ONESHOT ; /* turn ON the outputs */
|
||||
OutBuff[1] = 0xFF ; /* ALL outputs */
|
||||
OutBuff[2] = 0x04 ; /* Disable outputs (immediately) */
|
||||
OutBuff[3] = 0 ;
|
||||
OutBuff[4] = 0 ;
|
||||
OutBuff[5] = 0 ;
|
||||
OutBuff[6] = 0 ;
|
||||
OutBuff[7] = 0 ;
|
||||
if ((p = CmdSerial(OutBuff, LEN_SD_ONESHOT, InpBuff)) != NULL)
|
||||
{
|
||||
p += 3 ; /* 'p' points to received data */
|
||||
upslogx(LOG_INFO, "Turning load on.");
|
||||
upsdebugx(1, "'SdOneshot(turn_OFF)': %s", PrintErr(ups.ErrCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
upsdebugx(1, "'SdOneshot(turn_OFF)': %s", PrintErr(ups.ErrCode));
|
||||
upslogx(LOG_ERR, "'SdOneshot(turn_OFF)': %s", PrintErr(ups.ErrCode));
|
||||
}
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
|
||||
if (strcasecmp(cmdname, "shutdown.return") == 0)
|
||||
{
|
||||
OutBuff[0] = CMD_SD_ONESHOT ; /* turn ON the outputs */
|
||||
OutBuff[1] = 0xFF ; /* ALL outputs */
|
||||
if (ups.StatusUPS & 0x01)
|
||||
OutBuff[2] = 0x02 ; /* Battery shutdown */
|
||||
else
|
||||
OutBuff[2] = 0x01 ; /* Online shutdown */
|
||||
|
||||
if (ups.ShutdownDelay < 6)
|
||||
ups.ShutdownDelay = 6 ;
|
||||
|
||||
OutBuff[3] = (ups.ShutdownDelay >> 8) & 0xFF ; /* SDDELAY (MSB) Shutdown value (seconds) */
|
||||
OutBuff[4] = (ups.ShutdownDelay & 0xFF) ; /* SDDELAY (LSB) */
|
||||
OutBuff[5] = (ups.WakeUpDelay >> 16) & 0xFF ; /* WUDELAY (MSB) Wakeup value (seconds) */
|
||||
OutBuff[6] = (ups.WakeUpDelay >> 8) & 0xFF ; /* WUDELAY (...) */
|
||||
OutBuff[7] = (ups.WakeUpDelay & 0xFF ) ; /* WUDELAY (LSB) */
|
||||
|
||||
if ((p = CmdSerial(OutBuff, LEN_SD_ONESHOT, InpBuff)) != NULL)
|
||||
{
|
||||
p += 3 ; /* 'p' points to received data */
|
||||
upslogx(LOG_INFO, "Shutdown command(TYPE=%02x, SD=%u, WU=%u)", OutBuff[2], ups.ShutdownDelay, ups.WakeUpDelay) ;
|
||||
upsdebugx(3, "Shutdown command(TYPE=%02x, SD=%u, WU=%u): %s", OutBuff[2], ups.ShutdownDelay, ups.WakeUpDelay, PrintErr(ups.ErrCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
upsdebugx(1, "Shutdown command(TYPE=%02x, SD=%u, WU=%u): %s", OutBuff[2], ups.ShutdownDelay, ups.WakeUpDelay, PrintErr(ups.ErrCode));
|
||||
upslogx(LOG_ERR, "Shutdown command(SD=%u, WU=%u): %s", ups.ShutdownDelay, ups.WakeUpDelay, PrintErr(ups.ErrCode));
|
||||
}
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
|
||||
if (strcasecmp(cmdname, "shutdown.stayoff") == 0)
|
||||
{
|
||||
OutBuff[0] = CMD_SD_ONESHOT ; /* turn ON the outputs */
|
||||
OutBuff[1] = 0xFF ; /* ALL outputs */
|
||||
if (ups.StatusUPS & 0x01)
|
||||
OutBuff[2] = 0x02 ; /* Battery shutdown */
|
||||
else
|
||||
OutBuff[2] = 0x01 ; /* Online shutdown */
|
||||
|
||||
if (ups.ShutdownDelay < 6)
|
||||
ups.ShutdownDelay = 6 ;
|
||||
|
||||
OutBuff[3] = (ups.ShutdownDelay >> 8) & 0xFF ; /* SDDELAY (MSB) Shutdown value (seconds) */
|
||||
OutBuff[4] = (ups.ShutdownDelay & 0xFF) ; /* SDDELAY (LSB) */
|
||||
OutBuff[5] = 0 ; /* WUDELAY (MSB) Wakeup value (seconds) */
|
||||
OutBuff[6] = 0 ; /* WUDELAY (...) */
|
||||
OutBuff[7] = 0 ; /* WUDELAY (LSB) */
|
||||
|
||||
if ((p = CmdSerial(OutBuff, LEN_SD_ONESHOT, InpBuff)) != NULL)
|
||||
{
|
||||
p += 3 ; /* 'p' points to received data */
|
||||
upslogx(LOG_INFO, "shutdown.stayoff - (TYPE=%02x, SD=%u, WU=%u)", OutBuff[2], ups.ShutdownDelay, 0) ;
|
||||
upsdebugx(3, "shutdown.stayoff - (TYPE=%02x, SD=%u, WU=%u): %s", OutBuff[2], ups.ShutdownDelay, 0, PrintErr(ups.ErrCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
upsdebugx(1, "shutdown.stayoff - (TYPE=%02x, SD=%u, WU=%u): %s", OutBuff[2], ups.ShutdownDelay, 0, PrintErr(ups.ErrCode));
|
||||
upslogx(LOG_ERR, "shutdown.stayoff - (TYPE=%02x, SD=%u, WU=%u)", OutBuff[2], ups.ShutdownDelay, 0) ;
|
||||
}
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
}
|
||||
|
||||
int setvar(const char *varname, const char *val)
|
||||
{
|
||||
int delay;
|
||||
|
||||
if (sscanf(val, "%d", &delay) != 1)
|
||||
{
|
||||
return STAT_SET_UNKNOWN;
|
||||
}
|
||||
|
||||
if (strcasecmp(varname, "ups.delay.start") == 0)
|
||||
{
|
||||
delay = CLAMP(delay, 0, MAX_START_DELAY);
|
||||
upsdebugx(1, "set 'WUDELAY': %d/%d", delay, ups.WakeUpDelay);
|
||||
ups.WakeUpDelay = delay ;
|
||||
dstate_setinfo("ups.delay.start", "%d", ups.WakeUpDelay);
|
||||
dstate_dataok();
|
||||
return STAT_SET_HANDLED;
|
||||
}
|
||||
|
||||
if (strcasecmp(varname, "ups.delay.shutdown") == 0)
|
||||
{
|
||||
delay = CLAMP(delay, 0, MAX_SHUTDOWN_DELAY);
|
||||
upsdebugx(1, "set 'SDDELAY': %d/%d", delay, ups.ShutdownDelay);
|
||||
ups.ShutdownDelay = delay;
|
||||
dstate_setinfo("ups.delay.shutdown", "%d", ups.ShutdownDelay);
|
||||
dstate_dataok();
|
||||
return STAT_SET_HANDLED;
|
||||
}
|
||||
|
||||
return STAT_SET_UNKNOWN;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void upsdrv_initinfo(void)
|
||||
{
|
||||
/* Get vars from ups.conf */
|
||||
if (getval("ups.delay.shutdown")) {
|
||||
ups.ShutdownDelay = CLAMP(atoi(getval("ups.delay.shutdown")), 0, MAX_SHUTDOWN_DELAY);
|
||||
}
|
||||
else {
|
||||
ups.ShutdownDelay = 120; /* Shutdown delay in seconds */
|
||||
}
|
||||
|
||||
if (getval("ups.delay.start")) {
|
||||
ups.WakeUpDelay = CLAMP(atoi(getval("ups.delay.start")), 0, MAX_START_DELAY);
|
||||
}
|
||||
else {
|
||||
ups.WakeUpDelay = 10; /* WakeUp delay in seconds */
|
||||
}
|
||||
|
||||
if (detect_hardware() == -1)
|
||||
{
|
||||
fatalx(EXIT_FAILURE,
|
||||
"Unable to detect a Microdowell's Enterprise UPS on port %s\nCheck the cable, port name and try again", device_path);
|
||||
}
|
||||
|
||||
/* I set the correspondig UPS variables
|
||||
They were read in 'detect_hardware()'
|
||||
some other variables were set in 'detect_hardware()' */
|
||||
dstate_setinfo("ups.model", "Enterprise N%s", ups.UpsModel+3) ;
|
||||
dstate_setinfo("ups.power.nominal", "%d", atoi(ups.UpsModel+3) * 100) ;
|
||||
dstate_setinfo("ups.realpower.nominal", "%d", atoi(ups.UpsModel+3) * 60) ;
|
||||
|
||||
ups.ge_2kVA = 0 ; /* differentiate between 2 type of UPSs */
|
||||
if (atoi(ups.UpsModel+3) >= 20)
|
||||
ups.ge_2kVA = 1 ;
|
||||
|
||||
dstate_setinfo("ups.type", "online-interactive") ;
|
||||
dstate_setinfo("ups.serial", "%s", ups.SerialNumber) ;
|
||||
dstate_setinfo("ups.firmware", "%d.%d (%d)", ups.FW_MajorVersion, ups.FW_MinorVersion, ups.FW_SubVersion) ;
|
||||
dstate_setinfo("ups.firmware.aux", "%d.%d %d.%d", ups.HW_MajorVersion, ups.HW_MinorVersion,
|
||||
ups.BR_MajorVersion, ups.BR_MinorVersion) ;
|
||||
dstate_setinfo("ups.mfr", "Microdowell") ;
|
||||
dstate_setinfo("ups.mfr.date", "%04d/%02d/%02d", ups.YearOfProd, ups.MonthOfProd, ups.DayOfProd) ;
|
||||
dstate_setinfo("battery.packs", "%d", ups.BatteryNumber) ;
|
||||
|
||||
dstate_setinfo("driver.version.internal", "%s", DRIVER_VERSION) ;
|
||||
|
||||
/* Register the available variables. */
|
||||
dstate_setinfo("ups.delay.start", "%d", ups.WakeUpDelay);
|
||||
dstate_setflags("ups.delay.start", ST_FLAG_RW | ST_FLAG_STRING);
|
||||
dstate_setaux("ups.delay.start", MAX_START_DELAY_LEN);
|
||||
|
||||
dstate_setinfo("ups.delay.shutdown", "%d", ups.ShutdownDelay);
|
||||
dstate_setflags("ups.delay.shutdown", ST_FLAG_RW | ST_FLAG_STRING);
|
||||
dstate_setaux("ups.delay.shutdown", MAX_SHUTDOWN_DELAY_LEN);
|
||||
|
||||
dstate_addcmd("load.on");
|
||||
dstate_addcmd("load.off");
|
||||
dstate_addcmd("shutdown.return");
|
||||
dstate_addcmd("shutdown.stayoff");
|
||||
|
||||
|
||||
/* Register the available instant commands. */
|
||||
/* dstate_addcmd("test.battery.start");
|
||||
dstate_addcmd("test.battery.stop");
|
||||
dstate_addcmd("shutdown.stop");
|
||||
dstate_addcmd("beeper.toggle");
|
||||
*/
|
||||
|
||||
/* set handlers */
|
||||
upsh.instcmd = instcmd ;
|
||||
upsh.setvar = setvar;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
unsigned char OutBuff[20] ;
|
||||
unsigned char InpBuff[260] ;
|
||||
unsigned char *p ;
|
||||
unsigned char BatteryFlag=0 ;
|
||||
|
||||
OutBuff[0] = CMD_GET_STATUS ; /* get UPS status */
|
||||
if ((p = CmdSerial(OutBuff, LEN_GET_STATUS, InpBuff)) != NULL)
|
||||
{
|
||||
p += 3 ; /* 'p' points to received data */
|
||||
|
||||
status_init(); /* reset status flags */
|
||||
|
||||
/* store last UPS status */
|
||||
ups.StatusUPS = (int)p[0] | ((int)p[1]<<8) | ((int)p[2]<<16) | ((int)p[3]<<24) ;
|
||||
ups.ShortStatus = (int)p[0] | ((int)p[1]<<8) ;
|
||||
upsdebugx(1, "ups.StatusUPS: %08lX", ups.StatusUPS);
|
||||
upsdebugx(1, "ups.ShortStatus: %04X", ups.ShortStatus);
|
||||
|
||||
/* on battery? */
|
||||
if (p[0] & 0x01)
|
||||
BatteryFlag = 1 ; /* YES */
|
||||
upsdebugx(3, "get 'Get Status': %s", PrintErr(ups.ErrCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
upsdebugx(1, "get 'Get Status': %s", PrintErr(ups.ErrCode));
|
||||
/* upslogx(LOG_ERR, "get 'Get Status': %s", PrintErr(ups.ErrCode)); */
|
||||
}
|
||||
|
||||
|
||||
/* Send SHUTDOWN command */
|
||||
OutBuff[0] = CMD_SD_ONESHOT ; /* Send SHUTDOWN command */
|
||||
OutBuff[1] = 0xFF ; /* shutdown on ALL ports */
|
||||
|
||||
/* is the UPS on battery? */
|
||||
if (BatteryFlag)
|
||||
OutBuff[2] = 0x02 ; /* Type of shutdown (BATTERY MODE) */
|
||||
else
|
||||
OutBuff[2] = 0x01 ; /* Type of shutdown (ONLINE) */
|
||||
|
||||
if (ups.ShutdownDelay < 6)
|
||||
ups.ShutdownDelay = 6 ;
|
||||
|
||||
OutBuff[3] = (ups.ShutdownDelay >> 8) & 0xFF ; /* SDDELAY (MSB) Shutdown value (seconds) */
|
||||
OutBuff[4] = (ups.ShutdownDelay & 0xFF) ; /* SDDELAY (LSB) */
|
||||
OutBuff[5] = (ups.WakeUpDelay >> 16) & 0xFF ; /* WUDELAY (MSB) Wakeup value (seconds) */
|
||||
OutBuff[6] = (ups.WakeUpDelay >> 8) & 0xFF ; /* WUDELAY (...) */
|
||||
OutBuff[7] = (ups.WakeUpDelay & 0xFF ) ; /* WUDELAY (LSB) */
|
||||
|
||||
if ((p = CmdSerial(OutBuff, LEN_SD_ONESHOT, InpBuff)) != NULL)
|
||||
{
|
||||
upsdebugx(2, "Shutdown command(TYPE=%02x, SD=%u, WU=%u): %s", OutBuff[2], ups.ShutdownDelay, ups.WakeUpDelay, PrintErr(ups.ErrCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
/* command not sent: print error code */
|
||||
upsdebugx(1, "Shutdown command(TYPE=%02x, SD=%u, WU=%u): %s", OutBuff[2], ups.ShutdownDelay, ups.WakeUpDelay, PrintErr(ups.ErrCode));
|
||||
upslogx(LOG_ERR, "Shutdown command(SD=%u, WU=%u): %s", ups.ShutdownDelay, ups.WakeUpDelay, PrintErr(ups.ErrCode));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
}
|
||||
|
||||
/* list flags and values that you want to receive via -x */
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
/* allow '-x xyzzy' */
|
||||
/* addvar(VAR_FLAG, "xyzzy", "Enable xyzzy mode"); */
|
||||
|
||||
/* allow '-x foo=<some value>' */
|
||||
addvar(VAR_VALUE, "ups.delay.shutdown", "Override shutdown delay (120s)");
|
||||
addvar(VAR_VALUE, "ups.delay.start", "Override restart delay (10s)");
|
||||
}
|
||||
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
upsfd = ser_open(device_path) ;
|
||||
|
||||
ser_set_speed(upsfd, device_path, B19200) ;
|
||||
|
||||
/* need to clear RTS and DTR: otherwise with default cable, communication will be problematic
|
||||
It is the same as removing pin7 from cable (pin 7 is needed for Plug&Play compatibility) */
|
||||
ser_set_dtr(upsfd, 0);
|
||||
ser_set_rts(upsfd, 0);
|
||||
|
||||
usleep(10000) ; /* small delay (1/100 s)) */
|
||||
}
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
/* free(dynamic_mem); */
|
||||
ser_close(upsfd, device_path) ;
|
||||
}
|
||||
327
drivers/microdowell.h
Normal file
327
drivers/microdowell.h
Normal file
|
|
@ -0,0 +1,327 @@
|
|||
#ifndef MICRODOWELL_H
|
||||
#define MICRODOWELL_H
|
||||
|
||||
#ifdef ENTERPRISE_PROTOCOL
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
|
||||
#define STX_CHAR '['
|
||||
#define ERR_COM_NO_CHARS -999 // nessun carattere dalla porta seriale
|
||||
#define ERR_MSG_TOO_SHORT -998 // messaggio troppo breve: non arriva sino al Checksum
|
||||
#define ERR_MSG_CHECKSUM -997 // checksum non valido
|
||||
#define ERR_COM_TIMEOUT -996 // timeout in lettura
|
||||
#define ERR_CLR_RX_BUFF -900 // bisogna cancellare il buffer (non è un errore!)
|
||||
#define COMMAND_NOT_VALID -50 // comando non valido
|
||||
#define PARAMETER_NOT_VALID -51 // parametro non valido
|
||||
#define ERR_UPS_NOT_FOUND -10 // non trovo un UPS
|
||||
#define ERR_COM_PORT_OPEN -11 // impossibile aprire la porta di comunicazione
|
||||
#define ERR_COM_SET_PORT -12 // impossibile configurare la porta di comunicazione
|
||||
#define ERR_COM_UNDEF_PORT -20 // porta non valida o non esistente
|
||||
#define ERR_USB_DRIVER -13 // impossibile aprire il driver USB
|
||||
#define ERR_USB_COMM_KO -14 // interfaccia USB OK: gruppo spento o guasto
|
||||
#define ERR_UPS_UNKNOWN -30 // impossibile identificare l'UPS
|
||||
#define ERR_PRG_THREAD -40 // impossibile creare uno thread
|
||||
#define ERR_PRG_INVALID_DATA -60 // dati non validi
|
||||
|
||||
#define ERR_NO_ERROR 0x00 // no errors
|
||||
#define ERR_I2C_BUSY 0x01 // I2C bus busy (e2prom)
|
||||
#define ERR_CMD_CHECKSUM 0x10 // Checksum not valid
|
||||
#define ERR_CMD_UNRECOG 0x11 // unrecognized command
|
||||
#define ERR_EEP_NOBLOCK 0x08 // WRITE: eeprom address not multiple of 8
|
||||
#define ERR_EEP_OOBOUND 0x09 // READ: eeprom address out of bound (with size)
|
||||
#define ERR_EEP_WADDR1 0x28 // error writing e2prom address
|
||||
#define ERR_EEP_WSADDR1 0x29 // error writing e2prom subaddress
|
||||
#define ERR_EEP_RDATA 0x2A // error reading e2prom data
|
||||
#define ERR_EEP_WADDR2 0x50 // error writing e2prom address
|
||||
#define ERR_EEP_WSADDR2 0x51 // error reading e2prom subaddress
|
||||
#define ERR_EEP_WDATA 0x52 // error writing e2prom data
|
||||
#define ERR_EEP_WADDRVER 0x53 // error writing e2prom address during data verification
|
||||
#define ERR_EEP_WDATAVER 0x54 // error verification e2prom data
|
||||
#define ERR_EEP_VERIFY 0x55 // e2prom data are different from those in the write buffer
|
||||
#define ERR_EEP_CHECKSUM 0x90 // e2prom checksum error
|
||||
|
||||
|
||||
|
||||
#define CMD_ACK 0x06 // ACK
|
||||
#define CMD_NACK 0x15 // NACK
|
||||
|
||||
#define CMD_GET_STATUS 0x00 // comando di acquisizione STATUS
|
||||
#define CMD_GET_MEASURES 0x01 // comando di acquisizione MISURE
|
||||
#define CMD_GET_CONFIG 0x02 // comando di acquisizione CONFIGURAZIONE
|
||||
#define CMD_GET_BATT_STAT 0x03 // comando di acquisizione Battery+Load Status
|
||||
#define CMD_GET_BAT_LD 0x03 // comando di acquisizione Battery+Load Status
|
||||
#define CMD_GET_MASK 0x07 // comando di acquisizione MASCHERA PUNTI
|
||||
#define CMD_SET_TIMER 0x20 // comando di CONFIGURAZIONE TIMER
|
||||
#define CMD_BATT_TEST 0x21 // comando di CONFIGURAZIONE TEST BATTERIA
|
||||
#define CMD_GET_BATT_TEST 0x22 // comando di LETTURA TEST BATTERIA
|
||||
#define CMD_SD_ONESHOT 0x40 // comando di SCRITTURA SHUTDOWN ONESHOT
|
||||
#define CMD_GET_SD_ONESHOT 0x41 // comando di LETTURA SHUTDOWN ONESHOT
|
||||
#define CMD_SET_SCHEDULE 0x42 // comando di SCRITTURA SCHEDULE
|
||||
#define CMD_GET_SCHEDULE 0x43 // comando di LETTURA SCHEDULE
|
||||
#define CMD_GET_EEP_BLOCK 0x50 // comando di LETTURA BLOCCO EEPROM
|
||||
#define CMD_SET_EEP_BLOCK 0x51 // comando di SCRITTURA BLOCCO EEPROM
|
||||
#define CMD_GET_EEP_SEED 0x52 // comando di acquisizione SEME PROGR. EEPROM
|
||||
#define CMD_INIT 0xF0 // comando di REINIZIALIZZAZIONE UPS
|
||||
|
||||
|
||||
#define LEN_ACK 1 // ACK
|
||||
#define LEN_NACK 2 // NACK
|
||||
|
||||
#define LEN_GET_STATUS 1 // comando di acquisizione STATUS
|
||||
#define LEN_GET_MEASURES 1 // comando di acquisizione MISURE
|
||||
#define LEN_GET_CONFIG 1 // comando di acquisizione CONFIGURAZIONE
|
||||
#define LEN_GET_BATT_STAT 1 // comando di acquisizione Battery+Load Status
|
||||
#define LEN_GET_BAT_LD 1 // comando di acquisizione Battery+Load Status
|
||||
#define LEN_GET_MASK 1 // comando di acquisizione MASCHERA PUNTI
|
||||
#define LEN_SET_TIMER 5 // comando di CONFIGURAZIONE TIMER
|
||||
#define LEN_BATT_TEST 4 // comando di CONFIGURAZIONE TEST BATTERIA
|
||||
#define LEN_GET_BATT_TEST 1 // comando di LETTURA TEST BATTERIA
|
||||
#define LEN_SD_ONESHOT 8 // comando di SCRITTURA SHUTDOWN ONESHOT
|
||||
#define LEN_GET_SD_ONESHOT 1 // comando di LETTURA SHUTDOWN ONESHOT
|
||||
#define LEN_SET_SCHEDULE 8 // comando di SCRITTURA SCHEDULE
|
||||
#define LEN_GET_SCHEDULE 2 // comando di LETTURA SCHEDULE
|
||||
#define LEN_GET_EEP_BLOCK 3 // comando di SCRITTURA BLOCCO EEPROM
|
||||
#define LEN_SET_EEP_BLOCK 12 // comando di SCRITTURA BLOCCO EEPROM
|
||||
#define LEN_GET_EEP_SEED 1 // comando di acquisizione SEME PROGR. EEPROM
|
||||
#define LEN_INIT 1 // comando di REINIZIALIZZAZIONE UPS
|
||||
|
||||
|
||||
// non completamente definiti!
|
||||
#define RET_GET_STATUS 10 // comando di acquisizione STATUS
|
||||
#define RET_GET_MEASURES 15 // comando di acquisizione MISURE
|
||||
#define RET_GET_CONFIG 11 // comando di acquisizione CONFIGURAZIONE
|
||||
#define RET_GET_BATT_STAT 10 // comando di acquisizione Battery+Load Status
|
||||
#define RET_GET_BAT_LD 10 // comando di acquisizione Battery+Load Status
|
||||
#define RET_GET_MASK 1 // comando di acquisizione MASCHERA PUNTI
|
||||
#define RET_SET_TIMER 5 // comando di CONFIGURAZIONE TIMER
|
||||
#define RET_BATT_TEST 4 // comando di CONFIGURAZIONE TEST BATTERIA
|
||||
#define RET_GET_BATT_TEST 1 // comando di LETTURA TEST BATTERIA
|
||||
#define RET_SD_ONESHOT 8 // comando di SCRITTURA SHUTDOWN ONESHOT
|
||||
#define RET_GET_SD_ONESHOT 1 // comando di LETTURA SHUTDOWN ONESHOT
|
||||
#define RET_SET_SCHEDULE 8 // comando di SCRITTURA SCHEDULE
|
||||
#define RET_GET_SCHEDULE 8 // comando di LETTURA SCHEDULE
|
||||
#define RET_GET_EEP_BLOCK 11 // comando di SCRITTURA BLOCCO EEPROM
|
||||
#define RET_SET_EEP_BLOCK 12 // comando di SCRITTURA BLOCCO EEPROM
|
||||
#define RET_GET_EEP_SEED 1 // comando di acquisizione SEME PROGR. EEPROM
|
||||
#define RET_INIT 1 // comando di REINIZIALIZZAZIONE UPS
|
||||
|
||||
|
||||
//=======================================================
|
||||
// Indirizzi delle variabili memorizzate nella EEPROM: //
|
||||
//=======================================================
|
||||
#define EEP_OPT_BYTE_BLK 0x00 // Option Bytes block
|
||||
#define EEP_MAGIC 0x00 // magic number: deve essere a 0xAA
|
||||
#define EEP_CHECKSUM 0x01 // XOR longitudinale da 0x?? a 0x??
|
||||
#define EEP_OUT_CONFIG 0x02 // vedi documentazione
|
||||
#define EEP_OPT_BYTE_0 0x02 // vedi documentazione
|
||||
#define EEP_OPT_BYTE_1 0x03 // vedi documentazione
|
||||
#define EEP_STATUS 0x04 // Status UPS
|
||||
|
||||
#define EEP_THRESHOLD_0 0x08 // Blocco 0 con gli threshold
|
||||
#define EEP_MEAN_MIN 0x08 // UPS minimum AC voltage intervention point
|
||||
#define EEP_MEAN_MAX 0x0A // UPS maximum AC voltage intervention point
|
||||
#define EEP_AVR_MIN 0x0C // AVR minimum AC voltage intervention point
|
||||
#define EEP_AVR_MAX 0x0E // AVR maximum AC voltage intervention point
|
||||
|
||||
#define EEP_THRESHOLD_1 0x10 // Blocco 1 con gli threshold
|
||||
#define EEP_BLOW_VALUE 0x10 // Battery LOW threshold voltage
|
||||
#define EEP_BEND_VALUE 0x12 // Battery END threshold voltage
|
||||
#define EEP_VMETER_0 0x14 // Led 0 Voltage indicator threshold
|
||||
#define EEP_VMETER_1 0x16 // Led 1 Voltage indicator threshold
|
||||
|
||||
#define EEP_THRESHOLD_2 0x18 // Blocco 2 con gli threshold
|
||||
#define EEP_VMETER_2 0x18 // Led 2 Voltage indicator threshold
|
||||
#define EEP_VMETER_3 0x1A // Led 3 Voltage indicator threshold
|
||||
#define EEP_VMETER_4 0x1C // Led 4 Voltage indicator threshold
|
||||
|
||||
#define EEP_FAULT_POINTS 0x20 // number of fault points (in an half sine wave) after which USP enter battery mode
|
||||
#define EEP_CHARGER_MIN 0x21 // Threshold voltage for battery charger
|
||||
|
||||
#define EEP_TEMP_MAX 0x28 // Maximum temperature: over this value an alarm will be generated
|
||||
#define EEP_TEMP_FAULT 0x2A // Fault temperature: over this value, UPS will shut down immediately
|
||||
#define EEP_FANSPEED_0 0x2C // Temperature for fan activation (low speed)
|
||||
#define EEP_FANSPEED_1 0x2E // Temperature for fan activation (medium speed)
|
||||
|
||||
#define EEP_FANSPEED_2 0x30 // Temperature for fan activation (high speed)
|
||||
|
||||
#define EEP_IOUT 0x38 //
|
||||
#define EEP_IOUT_OVRLD 0x38 // Maximum current: over this value an alarm will be generated
|
||||
#define EEP_IOUT_FAULT 0x3A // Fault current: over this value, UPS will shut down immediately
|
||||
#define EEP_PWRMTR_0 0x3C // Led 0 Power indicator threshold
|
||||
#define EEP_PWRMTR_1 0x3E // Led 1 Power indicator threshold
|
||||
|
||||
#define EEP_PWRMTR_2 0x40 // Led 2 Power indicator threshold
|
||||
#define EEP_PWRMTR_3 0x42 // Led 3 Power indicator threshold
|
||||
#define EEP_PWRMTR_4 0x44 // Led 4 Power indicator threshold
|
||||
#define EEP_PWRMTR_5 0x46 // Between "Full power" and "Overload" Power indicator level
|
||||
|
||||
#define EEP_INPUT_MASK_0 0x48 // Half sine input mask (0-7 of 10 points)
|
||||
#define EEP_INPUT_MASK_8 0x50 // Half sine input mask (8-9 of 10 points)
|
||||
|
||||
#define EEP_MIN_VBATT 0x60 // Minimum battery voltaged reached from the last power-on
|
||||
#define EEP_RUNTIME_H 0x62 // Working time in minutes (max ~=32 years) (MSB first)
|
||||
#define EEP_BAT_TIME_M 0x65 // Time in Battery mode: seconds*2 (max ~=390 days). (MSB first)
|
||||
|
||||
|
||||
#define EEP_UPS_MODEL 0x80 // Model of the UPS in ASCII. For the Enterprise line, the structure is:
|
||||
// ENTxxyyy
|
||||
// where: xx = Power rating in watts/100 (zero padded at left)
|
||||
// yyy = model variants (if not defined, space padded to the right)
|
||||
|
||||
#define EEP_NETWRK_ID 0x88 // Network ID in ASCII (not used) - space padded
|
||||
#define EEP_NETWRK_NAME 0x90 // Network Name in ASCII (not used) - space padded
|
||||
#define EEP_SERIAL_NUM 0x98 // Serial Number in ASCII - zero padded (at left)
|
||||
|
||||
#define EEP_PROD_DATE 0xA0 //
|
||||
#define EEP_PROD_YEAR 0xA0 // Year of production (add 2000)
|
||||
#define EEP_PROD_WEEK 0xA1 // Week of production [0-51]
|
||||
#define EEP_PROD_HW_VER 0xA2 // Hardware version: 2 nibbles ? Major and Minor number.
|
||||
#define EEP_PROD_BRD_VER 0xA3 // Board version: 2 nibbles ? Major and Minor number.
|
||||
#define EEP_PROD_FW_VER 0xA4 // Firmware version: 2 nibbles ? Major and Minor number.
|
||||
#define EEP_PROD_FW_SVER 0xA5 // Firmware SUBversion: 2 nibbles ? Major and Minor number.
|
||||
#define EEP_PROD_BTTRS 0xA6 // number of batteries installed in the UPS
|
||||
|
||||
|
||||
#define EEP_BATT_SUBST 0xA8 // battery replacement block
|
||||
#define EEP_BATT_YEAR_0 0xA8 // Year of 1st battery replacement (add 2000)
|
||||
#define EEP_BATT_WEEK_0 0xA9 // Week of 1st battery replacement
|
||||
#define EEP_BATT_YEAR_1 0xAA // Year of 2nd battery replacement (add 2000)
|
||||
#define EEP_BATT_WEEK_1 0xAB // Week of 2nd battery replacement
|
||||
#define EEP_BATT_YEAR_2 0xAC // Year of 3rd battery replacement (add 2000)
|
||||
#define EEP_BATT_WEEK_2 0xAD // Week of 3rd battery replacement
|
||||
#define EEP_BATT_YEAR_3 0xAE // Year of 4th battery replacement (add 2000)
|
||||
#define EEP_BATT_WEEK_3 0xAF // Week of 4th battery replacement
|
||||
|
||||
|
||||
#define EEP_SCHEDULE 0xC8 // Start of SCHEDULING table
|
||||
#define EEP_SCHEDULE_0 0xC8 // 1st Scheduling slot
|
||||
#define EEP_SCHEDULE_1 0xD0 // 2nd Scheduling slot
|
||||
#define EEP_SCHEDULE_2 0xD8 // 3rd Scheduling slot
|
||||
#define EEP_SCHEDULE_3 0xE0 // 4th Scheduling slot
|
||||
#define EEP_SCHEDULE_4 0xE8 // 5th Scheduling slot
|
||||
#define EEP_SCHEDULE_5 0xF0 // 6th Scheduling slot
|
||||
|
||||
|
||||
#define BIT_NO_COMM_UPS 0x0001 // Event 2 - communications with the UPS lost
|
||||
#define BIT_COMM_OK 0x0002 // Event 3 - communications with the UPS reestablished
|
||||
#define BIT_BATTERY_MODE 0x0004 // Event 5 - the UPS is in Battery mode
|
||||
#define BIT_BATTERY_LOW 0x0008 // Event 6 - the UPS has a LOW battery level
|
||||
#define BIT_MAINS_OK 0x0010 // Event 7 - return of the mains
|
||||
#define BIT_OVERLOAD 0x0020 // Event 8 - the UPS is overloaded
|
||||
#define BIT_HIGH_TEMPERATURE 0x0040 // Event 9 - the UPS is in overtemperature
|
||||
#define BIT_GENERIC_FAULT 0x0080 // Event 11 - there is a generic fault
|
||||
#define BIT_SYSTEM_SHUTDOWN 0x0100 // Event 14 - the UPS will shut down IMMEDIATELY
|
||||
#define BIT_STANDBY 0x0200 // Event 32 - the UPS went in STANDBY MODE
|
||||
#define BIT_BATTERY_END 0x0400 // Event 33 - the UPS went in battery END
|
||||
#define BIT_EVENT_0 0x1000 // evento ??
|
||||
#define BIT_EVENT_1 0x2000 // evento ??
|
||||
#define BIT_FIRST_TIME 0x4000 // se è la prima volta che chiamo la funzione
|
||||
#define BIT_POLL_RESTART 0x8000 // se riesco ad identificare l'UPS dopo lo scollegamento, reinvio i dati
|
||||
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int Port ; // porta a cui è collegato l' UPS:
|
||||
// 0 = USB
|
||||
// n = COMn
|
||||
char Opened ; // BOOL flag che identifica se la porta è aperta
|
||||
int ErrCode ; // ultimo codice di errore in fase di lettura
|
||||
int ErrCount ; // conteggio degli errori
|
||||
unsigned char PollFlag ; // identifica se posso accedere all'UPS
|
||||
// 0x01: se TRUE, polling abilitato
|
||||
// 0x02: se TRUE, la routine di polling dati è attiva: devo attendere
|
||||
// 0x04: se TRUE, bisogna rileggere la configurazione del gruppo
|
||||
//------------------------------------------------------------------------
|
||||
unsigned long Counter ; // contatore: viene incrementato ad ogni nuovo POLL
|
||||
unsigned char CommStatus ; // stato delle comunicazioni
|
||||
unsigned char FramePointer ; // puntatore al carattere di START dei dati ricevuti
|
||||
//------------------------------------------------------------------------
|
||||
char UpsModel[9] ; // modello UPS (8 caratteri)
|
||||
unsigned char ge_2kVA ; // if more or equal to 2KVA
|
||||
char SerialNumber[9] ; // numero di serie dell'UPS
|
||||
|
||||
unsigned short int YearOfProd ; // anno di produzione dell'UPS
|
||||
unsigned char MonthOfProd ; // Mese di produzione dell'UPS
|
||||
unsigned char DayOfProd ; // Giorno di produzione dell'UPS
|
||||
unsigned char HW_MajorVersion ; // Hardware: Major version
|
||||
unsigned char HW_MinorVersion ; // Hardware: Minor version
|
||||
unsigned char BR_MajorVersion ; // BoardHardware: Major version
|
||||
unsigned char BR_MinorVersion ; // BoardHardware: Minor version
|
||||
unsigned char FW_MajorVersion ; // Firmware: Major version
|
||||
unsigned char FW_MinorVersion ; // Firmware: Minor version
|
||||
unsigned char FW_SubVersion ; // Firmware: SUBVERSION (special releases
|
||||
// for particular customers)
|
||||
|
||||
unsigned char BatteryNumber ; // number of batteries in UPS
|
||||
//------------------------------------------------------------------------
|
||||
unsigned long StatusUPS ; // flag di stato dell'UPS 4 byte): 1=TRUE
|
||||
// bit 0 => BATTERY_MODE
|
||||
// bit 1 => BATTERY_LOW
|
||||
// bit 2 => BATTERY_END
|
||||
// bit 3 => ONLINE (funzionamento in rete)
|
||||
// bit 4 => MAINS_ON (rete di ingresso presente)
|
||||
// bit 5 => STANDBY (UPS in modo Standby)
|
||||
// bit 6 => WAIT_MAINS (UPS in fase di INIT + attesa rete)
|
||||
// bit 7 => INIT (UPS in fase di inizializzazione)
|
||||
// ------
|
||||
// bit 8 => MASK_OK (semionda OK)
|
||||
// bit 9 => MEAN_OK (media tensione di ingresso OK)
|
||||
// bit 10 => SYNC_OK (sincronizzazione semionda OK)
|
||||
// bit 11 => FAULT (generico)
|
||||
// bit 12 => TEMP_MAX (superato livello critico di temperatura)
|
||||
// bit 13 => TEMP_FAULT (Fault da temperatura: UPS si spegne)
|
||||
// bit 14 => LOAD_MAX (soglia overload superata)
|
||||
// bit 15 => LOAD_FAULT (Fault da carico: UPS si spegne)
|
||||
// ------
|
||||
// bit 16 => INV_FAULT (Fault dell'inverter)
|
||||
// bit 17 => IINV_MAX (eccessiva corrente sull'inverter)
|
||||
// bit 18 => IINV_FAULT (Fault sulla corrente inverter: l'UPS si spegne)
|
||||
// bit 19 => 50_60Hz (1=60Hz)
|
||||
// bit 20 => EEP_FAULT (problemi con la EEPROM)
|
||||
// bit 21 => VOUT_FAULT (tensione uscita troppo bassa)
|
||||
// bit 22 => - non definito -
|
||||
// bit 23 => - non definito -
|
||||
// ------
|
||||
// bit 24 to 31 => - NON DEFINITI -
|
||||
|
||||
unsigned short int ShortStatus ; // the LSB 2 bytes of the status
|
||||
unsigned char OutConfig ; // stato uscite UPS
|
||||
float Vinput ; // tensione di INPUT in 1/10 di Volt
|
||||
float Voutput ; // tensione di OUTPUT in 1/10 di Volt
|
||||
float Temp ; // temperatura in 1/10 di grado
|
||||
float InpFreq ; // Frequenza di INPUT in 1/10 di Hz
|
||||
float OutFreq ; // Frequenza di OUTPUT in 1/10 di Hz
|
||||
float OutCurrent ; // Corrente di Output in 1/10 di A
|
||||
unsigned char LoadPerc ; // Percentuale di carico
|
||||
unsigned short int LoadVA ; // Carico in VA
|
||||
float ChgCurrent ; // corrente carica batterie
|
||||
|
||||
float Vbatt ; // tensione delle batterie in 1/10 di Volt
|
||||
unsigned char PercBatt ; // percentuale di carica della batteria
|
||||
unsigned short int RtimeEmpty; // minuti alla scarica della batteria
|
||||
unsigned char PercEmpty ; // percentuale alla scarica: 0%=scarica, 100%=carica
|
||||
unsigned char OutStatus ; // stato delle uscite
|
||||
|
||||
unsigned char Year ; //
|
||||
unsigned char Month ; //
|
||||
unsigned char Day ; //
|
||||
unsigned char WeekDay ; // Giorno della settimana
|
||||
unsigned char Hour ; // Ora
|
||||
unsigned char Min ; // minuti
|
||||
unsigned char Sec ; // secondi
|
||||
|
||||
unsigned char BattLowPerc ; // percentuale carica batteria quando va in BLOW
|
||||
unsigned long Rtime ; // numero di minuti di accensione dell'UPS
|
||||
unsigned long RtimeBatt ; // numero di secondi*2 in modalità batteria dall'accensione
|
||||
//------------------------------------------------------------------------
|
||||
unsigned int ShutdownDelay ; // Shutdown delay in seconds
|
||||
unsigned int WakeUpDelay ; // WakeUp delay in seconds
|
||||
} ENT_STRUCT ;
|
||||
|
||||
|
||||
|
||||
#endif // ENTERPRISE_PROTOCOL
|
||||
|
||||
#endif
|
||||
120
drivers/netvision-mib.c
Normal file
120
drivers/netvision-mib.c
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
/* netvision-mib.c - data to monitor Socomec Sicon UPS equipped
|
||||
* with Netvision WEB/SNMP card/external box with NUT
|
||||
*
|
||||
* Copyright (C) 2004
|
||||
* Thanos Chatziathanassiou <tchatzi@arx.net>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "netvision-mib.h"
|
||||
|
||||
#define NETVISION_MIB_VERSION "0.1"
|
||||
|
||||
/* SNMP OIDs set */
|
||||
#define NETVISION_OID_UPS_MIB ".1.3.6.1.4.1.4555.1.1.1.1"
|
||||
#define NETVISION_OID_UPSIDENTMODEL ".1.3.6.1.4.1.4555.1.1.1.1.1.1.0"
|
||||
#define NETVISION_OID_UPSIDENTFWVERSION ".1.3.6.1.4.1.4555.1.1.1.1.1.2.0"
|
||||
#define NETVISION_OID_UPSIDENTAGENTSWVERSION ".1.3.6.1.4.1.4555.1.1.1.1.1.3.0"
|
||||
#define NETVISION_OID_UPSIDENTUPSSERIALNUMBER ".1.3.6.1.4.1.4555.1.1.1.1.1.4.0"
|
||||
|
||||
/* UPS Battery */
|
||||
#define NETVISION_OID_BATTERYSTATUS ".1.3.6.1.4.1.4555.1.1.1.1.2.1.0"
|
||||
static info_lkp_t netvision_batt_info[] = {
|
||||
{ 2, "" }, /* battery normal */
|
||||
{ 3, "LB" }, /* battery low */
|
||||
{ 4, "LB" }, /* battery depleted */
|
||||
{ 5, "" }, /* battery discharging */
|
||||
{ 6, "RB" }, /* battery failure */
|
||||
{ 0, "NULL" }
|
||||
};
|
||||
|
||||
#define NETVISION_OID_SECONDSONBATTERY ".1.3.6.1.4.1.4555.1.1.1.1.2.2.0"
|
||||
#define NETVISION_OID_BATT_RUNTIME_REMAINING ".1.3.6.1.4.1.4555.1.1.1.1.2.3.0"
|
||||
#define NETVISION_OID_BATT_CHARGE ".1.3.6.1.4.1.4555.1.1.1.1.2.4.0"
|
||||
#define NETVISION_OID_BATT_VOLTS ".1.3.6.1.4.1.4555.1.1.1.1.2.5.0"
|
||||
|
||||
#define NETVISION_OID_INPUT_NUM_LINES ".1.3.6.1.4.1.4555.1.1.1.1.3.1.0" /* 1phase or 3phase UPS input */
|
||||
#define NETVISION_OID_OUTPUT_NUM_LINES ".1.3.6.1.4.1.4555.1.1.1.1.4.3.0" /* 1phase or 3phase UPS output */
|
||||
|
||||
/*
|
||||
three phase ups provide input/output/load for each phase
|
||||
in case of one-phase output, only _P1 should be used
|
||||
*/
|
||||
|
||||
#define NETVISION_OID_OUT_VOLTAGE_P1 ".1.3.6.1.4.1.4555.1.1.1.1.4.4.1.2.1"
|
||||
#define NETVISION_OID_OUT_CURRENT_P1 ".1.3.6.1.4.1.4555.1.1.1.1.4.4.1.3.1"
|
||||
#define NETVISION_OID_OUT_LOAD_PCT_P1 ".1.3.6.1.4.1.4555.1.1.1.1.4.4.1.4.1"
|
||||
#define NETVISION_OID_IN_VOLTAGE_P1 ".1.3.6.1.4.1.4555.1.1.1.1.3.3.1.5.1"
|
||||
|
||||
#define NETVISION_OID_OUT_VOLTAGE_P2 ".1.3.6.1.4.1.4555.1.1.1.1.4.4.1.2.2"
|
||||
#define NETVISION_OID_OUT_CURRENT_P2 ".1.3.6.1.4.1.4555.1.1.1.1.4.4.1.3.2"
|
||||
#define NETVISION_OID_OUT_LOAD_PCT_P2 ".1.3.6.1.4.1.4555.1.1.1.1.4.4.1.4.2"
|
||||
#define NETVISION_OID_IN_VOLTAGE_P2 ".1.3.6.1.4.1.4555.1.1.1.1.3.3.1.5.2"
|
||||
|
||||
#define NETVISION_OID_OUT_VOLTAGE_P3 ".1.3.6.1.4.1.4555.1.1.1.1.4.4.1.2.3"
|
||||
#define NETVISION_OID_OUT_CURRENT_P3 ".1.3.6.1.4.1.4555.1.1.1.1.4.4.1.3.3"
|
||||
#define NETVISION_OID_OUT_LOAD_PCT_P3 ".1.3.6.1.4.1.4555.1.1.1.1.4.4.1.4.3"
|
||||
#define NETVISION_OID_IN_VOLTAGE_P3 ".1.3.6.1.4.1.4555.1.1.1.1.3.3.1.5.3"
|
||||
|
||||
#define NETVISION_OID_OUTPUT_SOURCE ".1.3.6.1.4.1.4555.1.1.1.1.4.1.0"
|
||||
|
||||
static info_lkp_t netvision_output_info[] = {
|
||||
{ 1, "NULL" }, /* output source other */
|
||||
{ 2, "NULL" }, /* output source none */
|
||||
{ 3, "OL" }, /* output source normal */
|
||||
{ 4, "OL BYPASS" }, /* output source bypass */
|
||||
{ 5, "OB" }, /* output source battery */
|
||||
{ 6, "OL BOOST" }, /* output source booster */
|
||||
{ 7, "OL TRIM" }, /* output source reducer */
|
||||
{ 8, "NULL" }, /* output source standby */
|
||||
{ 9, "NULL" }, /* output source ecomode */
|
||||
};
|
||||
|
||||
/* Snmp2NUT lookup table */
|
||||
static snmp_info_t netvision_mib[] = {
|
||||
{ "ups.mfr", ST_FLAG_STRING, SU_INFOSIZE, NETVISION_OID_UPSIDENTAGENTSWVERSION, "SOCOMEC SICON UPS",
|
||||
SU_FLAG_STATIC | SU_FLAG_OK, NULL },
|
||||
{ "ups.model", ST_FLAG_STRING, SU_INFOSIZE, NETVISION_OID_UPSIDENTMODEL,
|
||||
"Generic SNMP UPS", SU_FLAG_STATIC | SU_FLAG_OK, NULL },
|
||||
{ "ups.serial", ST_FLAG_STRING, SU_INFOSIZE, NETVISION_OID_UPSIDENTUPSSERIALNUMBER, "",
|
||||
SU_FLAG_STATIC | SU_FLAG_OK, NULL },
|
||||
{ "ups.firmware.aux", ST_FLAG_STRING, SU_INFOSIZE, NETVISION_OID_UPSIDENTFWVERSION, "",
|
||||
SU_FLAG_STATIC | SU_FLAG_OK, NULL },
|
||||
{ "ups.status", ST_FLAG_STRING, SU_INFOSIZE, NETVISION_OID_BATTERYSTATUS, "",
|
||||
SU_FLAG_OK | SU_STATUS_BATT, &netvision_batt_info[0] },
|
||||
{ "ups.status", ST_FLAG_STRING, SU_INFOSIZE, NETVISION_OID_BATTERYSTATUS, "",
|
||||
SU_FLAG_OK | SU_STATUS_PWR, &netvision_output_info[0] },
|
||||
|
||||
/* ups load */
|
||||
{ "ups.load", 0, 1, NETVISION_OID_OUT_LOAD_PCT_P1, 0, SU_FLAG_OK, NULL },
|
||||
|
||||
/*ups input,output voltage, output frquency phase 1 */
|
||||
{ "input.voltage", 0, 0.1, NETVISION_OID_IN_VOLTAGE_P1, 0, SU_FLAG_OK, NULL },
|
||||
{ "output.voltage", 0, 0.1, NETVISION_OID_OUT_VOLTAGE_P1, 0, SU_FLAG_OK, NULL },
|
||||
{ "output.current", 0, 0.1, NETVISION_OID_OUT_CURRENT_P1, 0, SU_FLAG_OK, NULL },
|
||||
|
||||
/* battery info */
|
||||
{ "battery.charge", 0, 1, NETVISION_OID_BATT_CHARGE, "", SU_FLAG_OK, NULL },
|
||||
{ "battery.voltage", 0, 0.1, NETVISION_OID_BATT_VOLTS, "", SU_FLAG_OK, NULL },
|
||||
{ "battery.runtime", 0, 60, NETVISION_OID_BATT_RUNTIME_REMAINING, "", SU_FLAG_OK, NULL },
|
||||
|
||||
/* end of structure. */
|
||||
{ NULL, 0, 0, NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
mib2nut_info_t netvision = { "netvision", NETVISION_MIB_VERSION, "", NETVISION_OID_UPSIDENTMODEL, netvision_mib };
|
||||
9
drivers/netvision-mib.h
Normal file
9
drivers/netvision-mib.h
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#ifndef NETVISION_MIB_H
|
||||
#define NETVISION_MIB_H
|
||||
|
||||
#include "main.h"
|
||||
#include "snmp-ups.h"
|
||||
|
||||
extern mib2nut_info_t netvision;
|
||||
|
||||
#endif /* NETVISION_MIB_H */
|
||||
686
drivers/netxml-ups.c
Normal file
686
drivers/netxml-ups.c
Normal file
|
|
@ -0,0 +1,686 @@
|
|||
/* netxml-ups.c Driver routines for network XML UPS units
|
||||
|
||||
Copyright (C)
|
||||
2008-2009 Arjen de Korte <adkorte-guest@alioth.debian.org>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "netxml-ups.h"
|
||||
#include "mge-xml.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <ne_request.h>
|
||||
#include <ne_basic.h>
|
||||
#include <ne_props.h>
|
||||
#include <ne_uri.h>
|
||||
#include <ne_xml.h>
|
||||
#include <ne_xmlreq.h>
|
||||
#include <ne_ssl.h>
|
||||
#include <ne_auth.h>
|
||||
#include <ne_socket.h>
|
||||
|
||||
#define DRIVER_NAME "network XML UPS"
|
||||
#define DRIVER_VERSION "0.30"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Arjen de Korte <adkorte-guest@alioth.debian.org>",
|
||||
DRV_EXPERIMENTAL,
|
||||
{ NULL }
|
||||
};
|
||||
/* FIXME:
|
||||
* "built with neon library %s" LIBNEON_VERSION
|
||||
* subdrivers (limited to MGE only ATM) */
|
||||
|
||||
/* Global vars */
|
||||
uint32_t ups_status = 0;
|
||||
static int timeout = 5;
|
||||
static time_t lastheard = 0;
|
||||
static subdriver_t *subdriver = &mge_xml_subdriver;
|
||||
static ne_session *session = NULL;
|
||||
static ne_socket *sock = NULL;
|
||||
static ne_uri uri;
|
||||
|
||||
/* Support functions */
|
||||
static void netxml_alarm_set(void);
|
||||
static void netxml_status_set(void);
|
||||
static int netxml_authenticate(void *userdata, const char *realm, int attempt, char *username, char *password);
|
||||
static int netxml_dispatch_request(ne_request *request, ne_xml_parser *parser);
|
||||
static int netxml_get_page(const char *page);
|
||||
|
||||
static int netxml_alarm_subscribe(const char *page);
|
||||
|
||||
#if HAVE_NE_SET_CONNECT_TIMEOUT && HAVE_NE_SOCK_CONNECT_TIMEOUT
|
||||
/* we don't need to use alarm() */
|
||||
#else
|
||||
static void netxml_alarm_handler(int sig)
|
||||
{
|
||||
/* don't do anything here, just return */
|
||||
}
|
||||
#endif
|
||||
|
||||
void upsdrv_initinfo(void)
|
||||
{
|
||||
char *page, *last = NULL;
|
||||
char buf[SMALLBUF];
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s", subdriver->initinfo);
|
||||
|
||||
for (page = strtok_r(buf, " ", &last); page != NULL; page = strtok_r(NULL, " ", &last)) {
|
||||
|
||||
if (netxml_get_page(page) != NE_OK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
dstate_setinfo("driver.version.internal", "%s", subdriver->version);
|
||||
|
||||
if (testvar("subscribe") && (netxml_alarm_subscribe(subdriver->subscribe) == NE_OK)) {
|
||||
extrafd = ne_sock_fd(sock);
|
||||
time(&lastheard);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
fatalx(EXIT_FAILURE, "%s: communication failure [%s]", __func__, ne_get_error(session));
|
||||
}
|
||||
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
int ret, errors = 0;
|
||||
|
||||
/* We really should be dealing with alarms through a separate callback, so that we can keep the
|
||||
* processing of alarms and polling for data separated. Currently, this isn't supported by the
|
||||
* driver main body, so we'll have to revert to polling each time we're called, unless the
|
||||
* socket indicates we're no longer connected.
|
||||
*/
|
||||
if (testvar("subscribe")) {
|
||||
char buf[LARGEBUF];
|
||||
|
||||
ret = ne_sock_read(sock, buf, sizeof(buf));
|
||||
|
||||
if (ret > 0) {
|
||||
/* alarm message received */
|
||||
|
||||
ne_xml_parser *parser = ne_xml_create();
|
||||
upsdebugx(2, "%s: ne_sock_read(%d bytes) => %s", __func__, ret, buf);
|
||||
ne_xml_push_handler(parser, subdriver->startelm_cb, subdriver->cdata_cb, subdriver->endelm_cb, NULL);
|
||||
ne_xml_parse(parser, buf, strlen(buf));
|
||||
ne_xml_destroy(parser);
|
||||
time(&lastheard);
|
||||
|
||||
} else if ((ret == NE_SOCK_TIMEOUT) && (difftime(time(NULL), lastheard) < 180)) {
|
||||
/* timed out */
|
||||
|
||||
upsdebugx(2, "%s: ne_sock_read(timeout)", __func__);
|
||||
|
||||
} else {
|
||||
/* connection closed or unknown error */
|
||||
|
||||
upslogx(LOG_ERR, "NSM connection with '%s' lost", uri.host);
|
||||
|
||||
upsdebugx(2, "%s: ne_sock_read(%d) => %s", __func__, ret, ne_sock_error(sock));
|
||||
ne_sock_close(sock);
|
||||
|
||||
if (netxml_alarm_subscribe(subdriver->subscribe) == NE_OK) {
|
||||
extrafd = ne_sock_fd(sock);
|
||||
time(&lastheard);
|
||||
return;
|
||||
}
|
||||
|
||||
dstate_datastale();
|
||||
extrafd = -1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* get additional data */
|
||||
ret = netxml_get_page(subdriver->getobject);
|
||||
if (ret != NE_OK) {
|
||||
errors++;
|
||||
}
|
||||
|
||||
ret = netxml_get_page(subdriver->summary);
|
||||
if (ret != NE_OK) {
|
||||
errors++;
|
||||
}
|
||||
|
||||
if (errors > 1) {
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
|
||||
status_init();
|
||||
|
||||
alarm_init();
|
||||
netxml_alarm_set();
|
||||
alarm_commit();
|
||||
|
||||
netxml_status_set();
|
||||
status_commit();
|
||||
|
||||
dstate_dataok();
|
||||
}
|
||||
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
/* tell the UPS to shut down, then return - DO NOT SLEEP HERE */
|
||||
|
||||
/* maybe try to detect the UPS here, but try a shutdown even if
|
||||
it doesn't respond at first if possible */
|
||||
|
||||
/* replace with a proper shutdown function */
|
||||
fatalx(EXIT_FAILURE, "shutdown not supported");
|
||||
|
||||
/* you may have to check the line status since the commands
|
||||
for toggling power are frequently different for OL vs. OB */
|
||||
|
||||
/* OL: this must power cycle the load if possible */
|
||||
|
||||
/* OB: the load must remain off until the power returns */
|
||||
}
|
||||
|
||||
/*
|
||||
static int instcmd(const char *cmdname, const char *extra)
|
||||
{
|
||||
if (!strcasecmp(cmdname, "test.battery.stop")) {
|
||||
ser_send_buf(upsfd, ...);
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
upslogx(LOG_NOTICE, "%s: unknown command [%s]", __func__, cmdname);
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
static int setvar(const char *varname, const char *val)
|
||||
{
|
||||
if (!strcasecmp(varname, "ups.test.interval")) {
|
||||
ser_send_buf(upsfd, ...);
|
||||
return STAT_SET_HANDLED;
|
||||
}
|
||||
|
||||
upslogx(LOG_NOTICE, "%s: unknown variable [%s]", __func__, varname);
|
||||
return STAT_SET_UNKNOWN;
|
||||
}
|
||||
*/
|
||||
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
}
|
||||
|
||||
/* list flags and values that you want to receive via -x */
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
char buf[SMALLBUF];
|
||||
|
||||
snprintf(buf, sizeof(buf), "network timeout (default: %d seconds)", timeout);
|
||||
addvar(VAR_VALUE, "timeout", buf);
|
||||
|
||||
addvar(VAR_FLAG, "subscribe", "NSM subscribe to NMC");
|
||||
|
||||
addvar(VAR_VALUE | VAR_SENSITIVE, "login", "login value for authenticated mode");
|
||||
addvar(VAR_VALUE | VAR_SENSITIVE, "password", "password value for authenticated mode");
|
||||
}
|
||||
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
int ret;
|
||||
char *val;
|
||||
FILE *fp;
|
||||
|
||||
#if HAVE_NE_SET_CONNECT_TIMEOUT && HAVE_NE_SOCK_CONNECT_TIMEOUT
|
||||
/* we don't need to use alarm() */
|
||||
#else
|
||||
struct sigaction sa;
|
||||
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = 0;
|
||||
|
||||
sa.sa_handler = netxml_alarm_handler;
|
||||
sigaction(SIGALRM, &sa, NULL);
|
||||
#endif
|
||||
/* allow override of default network timeout value */
|
||||
val = getval("timeout");
|
||||
if (val) {
|
||||
timeout = atoi(val);
|
||||
|
||||
if (timeout < 1) {
|
||||
fatalx(EXIT_FAILURE, "timeout must be greater than 0");
|
||||
}
|
||||
}
|
||||
|
||||
if (nut_debug_level > 5) {
|
||||
ne_debug_init(stderr, NE_DBG_HTTP | NE_DBG_HTTPBODY);
|
||||
}
|
||||
|
||||
if (ne_sock_init()) {
|
||||
fatalx(EXIT_FAILURE, "%s: failed to initialize socket libraries", progname);
|
||||
}
|
||||
|
||||
if (ne_uri_parse(device_path, &uri) || uri.host == NULL) {
|
||||
fatalx(EXIT_FAILURE, "%s: invalid hostname '%s'", progname, device_path);
|
||||
}
|
||||
/*
|
||||
if (uri.scheme == NULL) {
|
||||
uri.scheme = strdup("http");
|
||||
}
|
||||
|
||||
if (uri.host == NULL) {
|
||||
uri.host = strdup(device_path);
|
||||
}
|
||||
*/
|
||||
if (uri.port == 0) {
|
||||
uri.port = ne_uri_defaultport(uri.scheme);
|
||||
}
|
||||
|
||||
upsdebugx(1, "using %s://%s port %d", uri.scheme, uri.host, uri.port);
|
||||
|
||||
session = ne_session_create(uri.scheme, uri.host, uri.port);
|
||||
|
||||
/* timeout if we can't (re)connect to the UPS */
|
||||
#ifdef HAVE_NE_SET_CONNECT_TIMEOUT
|
||||
ne_set_connect_timeout(session, timeout);
|
||||
#endif
|
||||
|
||||
/* just wait for a couple of seconds */
|
||||
ne_set_read_timeout(session, timeout);
|
||||
|
||||
ne_set_useragent(session, subdriver->version);
|
||||
|
||||
if (strcasecmp(uri.scheme, "https") == 0) {
|
||||
ne_ssl_trust_default_ca(session);
|
||||
}
|
||||
|
||||
ne_set_server_auth(session, netxml_authenticate, NULL);
|
||||
|
||||
/* if debug level is set, direct output to stderr */
|
||||
if (!nut_debug_level) {
|
||||
fp = fopen("/dev/null", "w");
|
||||
} else {
|
||||
fp = stderr;
|
||||
}
|
||||
|
||||
if (!fp) {
|
||||
fatal_with_errno(EXIT_FAILURE, "Connectivity test failed");
|
||||
}
|
||||
|
||||
/* see if we have a connection */
|
||||
ret = ne_get(session, subdriver->initups, fileno(fp));
|
||||
|
||||
if (!nut_debug_level) {
|
||||
fclose(fp);
|
||||
} else {
|
||||
fprintf(fp, "\n");
|
||||
}
|
||||
|
||||
if (ret != NE_OK) {
|
||||
fatalx(EXIT_FAILURE, "Connectivity test: %s", ne_get_error(session));
|
||||
}
|
||||
|
||||
upslogx(LOG_INFO, "Connectivity test: %s", ne_get_error(session));
|
||||
}
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
free(subdriver->configure);
|
||||
free(subdriver->subscribe);
|
||||
free(subdriver->summary);
|
||||
free(subdriver->getobject);
|
||||
free(subdriver->setobject);
|
||||
|
||||
if (sock) {
|
||||
ne_sock_close(sock);
|
||||
}
|
||||
|
||||
if (session) {
|
||||
ne_session_destroy(session);
|
||||
}
|
||||
|
||||
ne_uri_free(&uri);
|
||||
}
|
||||
|
||||
/**********************************************************************
|
||||
* Support functions
|
||||
*********************************************************************/
|
||||
|
||||
static int netxml_get_page(const char *page)
|
||||
{
|
||||
int ret;
|
||||
ne_request *request;
|
||||
ne_xml_parser *parser;
|
||||
|
||||
upsdebugx(2, "%s: %s", __func__, page);
|
||||
|
||||
request = ne_request_create(session, "GET", page);
|
||||
|
||||
parser = ne_xml_create();
|
||||
|
||||
ne_xml_push_handler(parser, subdriver->startelm_cb, subdriver->cdata_cb, subdriver->endelm_cb, NULL);
|
||||
|
||||
ret = netxml_dispatch_request(request, parser);
|
||||
|
||||
if (ret) {
|
||||
upsdebugx(2, "%s: %s", __func__, ne_get_error(session));
|
||||
}
|
||||
|
||||
ne_xml_destroy(parser);
|
||||
ne_request_destroy(request);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int netxml_alarm_subscribe(const char *page)
|
||||
{
|
||||
int ret, port = -1, secret = -1;
|
||||
char buf[LARGEBUF], *s;
|
||||
ne_request *request;
|
||||
ne_sock_addr *addr;
|
||||
const ne_inet_addr *ai;
|
||||
|
||||
upsdebugx(2, "%s: %s", __func__, page);
|
||||
|
||||
sock = ne_sock_create();
|
||||
|
||||
if (gethostname(buf, sizeof(buf)) == 0) {
|
||||
dstate_setinfo("driver.hostname", "%s", buf);
|
||||
} else {
|
||||
dstate_setinfo("driver.hostname", "<unknown>");
|
||||
}
|
||||
|
||||
#ifdef HAVE_NE_SOCK_CONNECT_TIMEOUT
|
||||
ne_sock_connect_timeout(sock, timeout);
|
||||
#endif
|
||||
ne_sock_read_timeout(sock, 1);
|
||||
|
||||
netxml_get_page(subdriver->configure);
|
||||
|
||||
snprintf(buf, sizeof(buf), "<?xml version=\"1.0\">\n");
|
||||
snprintfcat(buf, sizeof(buf), "<Subscribe>\n");
|
||||
snprintfcat(buf, sizeof(buf), "<Class>%s v%s</Class>\n", progname, DRIVER_VERSION);
|
||||
snprintfcat(buf, sizeof(buf), "<Type>connected socket</Type>\n");
|
||||
snprintfcat(buf, sizeof(buf), "<HostName>%s</HostName>\n", dstate_getinfo("driver.hostname"));
|
||||
snprintfcat(buf, sizeof(buf), "<XMLClientParameters>\n");
|
||||
snprintfcat(buf, sizeof(buf), "<ShutdownDuration>%s</ShutdownDuration>\n", dstate_getinfo("driver.delay.shutdown"));
|
||||
snprintfcat(buf, sizeof(buf), "<ShutdownTimer>%s</ShutdownTimer>\n", dstate_getinfo("driver.timer.shutdown"));
|
||||
snprintfcat(buf, sizeof(buf), "<AutoConfig>CENTRALIZED</AutoConfig>\n");
|
||||
snprintfcat(buf, sizeof(buf), "<OutletGroup>1</OutletGroup>\n");
|
||||
snprintfcat(buf, sizeof(buf), "</XMLClientParameters>\n");
|
||||
/* snprintfcat(buf, sizeof(buf), "<Warning>NUT driver</Warning>\n"); */
|
||||
snprintfcat(buf, sizeof(buf), "</Subscribe>\n");
|
||||
|
||||
/* now send subscription message setting all the proper flags */
|
||||
request = ne_request_create(session, "POST", page);
|
||||
ne_set_request_body_buffer(request, buf, strlen(buf));
|
||||
|
||||
/* as the NMC reply is not xml standard compliant let's parse it this way */
|
||||
do {
|
||||
#ifndef HAVE_NE_SOCK_CONNECT_TIMEOUT
|
||||
alarm(timeout+1);
|
||||
#endif
|
||||
ret = ne_begin_request(request);
|
||||
|
||||
#ifndef HAVE_NE_SOCK_CONNECT_TIMEOUT
|
||||
alarm(0);
|
||||
#endif
|
||||
if (ret != NE_OK) {
|
||||
break;
|
||||
}
|
||||
|
||||
ret = ne_read_response_block(request, buf, sizeof buf);
|
||||
|
||||
if (ret == NE_OK) {
|
||||
ret = ne_end_request(request);
|
||||
}
|
||||
|
||||
} while (ret == NE_RETRY);
|
||||
|
||||
ne_request_destroy(request);
|
||||
|
||||
/* due to different formats used by the various NMCs, we need to\
|
||||
break up the reply in lines and parse each one separately */
|
||||
for (s = strtok(buf, "\r\n"); s != NULL; s = strtok(NULL, "\r\n")) {
|
||||
upsdebugx(2, "%s: parsing %s", __func__, s);
|
||||
|
||||
if (!strncasecmp(s, "<Port>", 6) && (sscanf(s+6, "%u", &port) != 1)) {
|
||||
return NE_RETRY;
|
||||
}
|
||||
|
||||
if (!strncasecmp(s, "<Secret>", 8) && (sscanf(s+8, "%u", &secret) != 1)) {
|
||||
return NE_RETRY;
|
||||
}
|
||||
}
|
||||
|
||||
if ((port == -1) || (secret == -1)) {
|
||||
upsdebugx(2, "%s: parsing initial subcription failed", __func__);
|
||||
return NE_RETRY;
|
||||
}
|
||||
|
||||
/* Resolve the given hostname. 'flags' must be zero. Hex
|
||||
* string IPv6 addresses (e.g. `::1') may be enclosed in brackets
|
||||
* (e.g. `[::1]'). */
|
||||
addr = ne_addr_resolve(uri.host, 0);
|
||||
|
||||
/* Returns zero if name resolution was successful, non-zero on
|
||||
* error. */
|
||||
if (ne_addr_result(addr) != 0) {
|
||||
upsdebugx(2, "%s: name resolution failure on %s: %s", __func__, uri.host, ne_addr_error(addr, buf, sizeof(buf)));
|
||||
ne_addr_destroy(addr);
|
||||
return NE_RETRY;
|
||||
}
|
||||
|
||||
for (ai = ne_addr_first(addr); ai != NULL; ai = ne_addr_next(addr)) {
|
||||
|
||||
upsdebugx(2, "%s: connecting to host %s port %d", __func__, ne_iaddr_print(ai, buf, sizeof(buf)), port);
|
||||
|
||||
#ifndef HAVE_NE_SOCK_CONNECT_TIMEOUT
|
||||
alarm(timeout+1);
|
||||
#endif
|
||||
ret = ne_sock_connect(sock, ai, port);
|
||||
|
||||
#ifndef HAVE_NE_SOCK_CONNECT_TIMEOUT
|
||||
alarm(0);
|
||||
#endif
|
||||
if (ret == NE_OK) {
|
||||
upsdebugx(2, "%s: connection to %s open on fd %d", __func__, uri.host, ne_sock_fd(sock));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ne_addr_destroy(addr);
|
||||
|
||||
if (ai == NULL) {
|
||||
upsdebugx(2, "%s: failed to create listening socket", __func__);
|
||||
return NE_RETRY;
|
||||
}
|
||||
|
||||
snprintf(buf, sizeof(buf), "<Subscription Identification=\"%u\"></Subscription>\n", secret);
|
||||
ret = ne_sock_fullwrite(sock, buf, strlen(buf));
|
||||
|
||||
if (ret != NE_OK) {
|
||||
upsdebugx(2, "%s: send failed: %s", __func__, ne_sock_error(sock));
|
||||
return NE_RETRY;
|
||||
}
|
||||
|
||||
ret = ne_sock_read(sock, buf, sizeof(buf));
|
||||
|
||||
if (ret < 1) {
|
||||
upsdebugx(2, "%s: read failed: %s", __func__, ne_sock_error(sock));
|
||||
return NE_RETRY;
|
||||
}
|
||||
|
||||
if (strcasecmp(buf, "<Subscription Answer=\"ok\"></Subscription>")) {
|
||||
upsdebugx(2, "%s: subscription rejected", __func__);
|
||||
return NE_RETRY;
|
||||
}
|
||||
|
||||
upslogx(LOG_INFO, "NSM connection to '%s' established", uri.host);
|
||||
return NE_OK;
|
||||
}
|
||||
|
||||
static int netxml_dispatch_request(ne_request *request, ne_xml_parser *parser)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Starting with neon-0.27.0 the ne_xml_dispatch_request() function will check
|
||||
* for a valid XML content-type (following RFC 3023 rules) in the header.
|
||||
* Unfortunately, (at least) the Transverse NMC doesn't follow this RFC, so
|
||||
* we can't use this anymore and we'll have to roll our own here.
|
||||
*/
|
||||
do {
|
||||
#ifndef HAVE_NE_SET_CONNECT_TIMEOUT
|
||||
alarm(timeout+1);
|
||||
#endif
|
||||
ret = ne_begin_request(request);
|
||||
|
||||
#ifndef HAVE_NE_SET_CONNECT_TIMEOUT
|
||||
alarm(0);
|
||||
#endif
|
||||
if (ret != NE_OK) {
|
||||
break;
|
||||
}
|
||||
|
||||
ret = ne_xml_parse_response(request, parser);
|
||||
|
||||
if (ret == NE_OK) {
|
||||
ret = ne_end_request(request);
|
||||
}
|
||||
|
||||
} while (ret == NE_RETRY);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Supply the 'login' and 'password' when authentication is required */
|
||||
static int netxml_authenticate(void *userdata, const char *realm, int attempt, char *username, char *password)
|
||||
{
|
||||
char *val;
|
||||
|
||||
upsdebugx(2, "%s: realm = [%s], attempt = %d", __func__, realm, attempt);
|
||||
|
||||
val = getval("login");
|
||||
snprintf(username, NE_ABUFSIZ, "%s", val ? val : "");
|
||||
|
||||
val = getval("password");
|
||||
snprintf(password, NE_ABUFSIZ, "%s", val ? val : "");
|
||||
|
||||
return attempt;
|
||||
}
|
||||
|
||||
/* Convert the local status information to NUT format and set NUT
|
||||
alarms. */
|
||||
static void netxml_alarm_set(void)
|
||||
{
|
||||
if (STATUS_BIT(REPLACEBATT)) {
|
||||
alarm_set("Replace battery!");
|
||||
}
|
||||
if (STATUS_BIT(SHUTDOWNIMM)) {
|
||||
alarm_set("Shutdown imminent!");
|
||||
}
|
||||
if (STATUS_BIT(FANFAIL)) {
|
||||
alarm_set("Fan failure!");
|
||||
}
|
||||
if (STATUS_BIT(NOBATTERY)) {
|
||||
alarm_set("No battery installed!");
|
||||
}
|
||||
if (STATUS_BIT(BATTVOLTLO)) {
|
||||
alarm_set("Battery voltage too low!");
|
||||
}
|
||||
if (STATUS_BIT(BATTVOLTHI)) {
|
||||
alarm_set("Battery voltage too high!");
|
||||
}
|
||||
if (STATUS_BIT(CHARGERFAIL)) {
|
||||
alarm_set("Battery charger fail!");
|
||||
}
|
||||
if (STATUS_BIT(OVERHEAT)) {
|
||||
alarm_set("Temperature too high!");
|
||||
}
|
||||
if (STATUS_BIT(COMMFAULT)) {
|
||||
alarm_set("Communication fault!");
|
||||
}
|
||||
if (STATUS_BIT(INTERNALFAULT)) {
|
||||
alarm_set("Internal UPS fault!");
|
||||
}
|
||||
if (STATUS_BIT(FUSEFAULT)) {
|
||||
alarm_set("Fuse fault!");
|
||||
}
|
||||
if (STATUS_BIT(BYPASSAUTO)) {
|
||||
alarm_set("Automatic bypass mode!");
|
||||
}
|
||||
if (STATUS_BIT(BYPASSMAN)) {
|
||||
alarm_set("Manual bypass mode!");
|
||||
}
|
||||
}
|
||||
|
||||
/* Convert the local status information to NUT format and set NUT
|
||||
status. */
|
||||
static void netxml_status_set(void)
|
||||
{
|
||||
if (STATUS_BIT(VRANGE)) {
|
||||
dstate_setinfo("input.transfer.reason", "input voltage out of range");
|
||||
} else if (STATUS_BIT(FRANGE)) {
|
||||
dstate_setinfo("input.transfer.reason", "input frequency out of range");
|
||||
} else {
|
||||
dstate_delinfo("input.transfer.reason");
|
||||
}
|
||||
|
||||
if (STATUS_BIT(ONLINE)) {
|
||||
status_set("OL"); /* on line */
|
||||
} else {
|
||||
status_set("OB"); /* on battery */
|
||||
}
|
||||
if (STATUS_BIT(DISCHRG)) {
|
||||
status_set("DISCHRG"); /* discharging */
|
||||
}
|
||||
if (STATUS_BIT(CHRG)) {
|
||||
status_set("CHRG"); /* charging */
|
||||
}
|
||||
if (STATUS_BIT(LOWBATT)) {
|
||||
status_set("LB"); /* low battery */
|
||||
}
|
||||
if (STATUS_BIT(OVERLOAD)) {
|
||||
status_set("OVER"); /* overload */
|
||||
}
|
||||
if (STATUS_BIT(REPLACEBATT)) {
|
||||
status_set("RB"); /* replace batt */
|
||||
}
|
||||
if (STATUS_BIT(TRIM)) {
|
||||
status_set("TRIM"); /* SmartTrim */
|
||||
}
|
||||
if (STATUS_BIT(BOOST)) {
|
||||
status_set("BOOST"); /* SmartBoost */
|
||||
}
|
||||
if (STATUS_BIT(BYPASSAUTO) || STATUS_BIT(BYPASSMAN)) {
|
||||
status_set("BYPASS"); /* on bypass */
|
||||
}
|
||||
if (STATUS_BIT(OFF)) {
|
||||
status_set("OFF"); /* ups is off */
|
||||
}
|
||||
|
||||
if (STATUS_BIT(SHUTDOWNIMM)) {
|
||||
status_set("FSD"); /* shutdown imminent */
|
||||
}
|
||||
}
|
||||
|
||||
77
drivers/netxml-ups.h
Normal file
77
drivers/netxml-ups.h
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/* netxml-ups.h Driver data/defines for network XML UPS units
|
||||
|
||||
Copyright (C)
|
||||
2008-2009 Arjen de Korte <adkorte-guest@alioth.debian.org>
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
#ifndef NETXML_UPS_H
|
||||
#define NETXML_UPS_H
|
||||
|
||||
#include "nut_stdint.h"
|
||||
|
||||
struct subdriver_s {
|
||||
const char *version; /* name of this subdriver */
|
||||
char *initups;
|
||||
char *initinfo;
|
||||
char *configure; /* central configuration */
|
||||
char *subscribe; /* alarm subscriptions */
|
||||
char *summary; /* used for quick updates */
|
||||
char *getobject;
|
||||
char *setobject;
|
||||
int (*startelm_cb)(void *userdata, int parent, const char *nspace, const char *name, const char **atts);
|
||||
int (*cdata_cb)(void *userdata, int state, const char *cdata, size_t len);
|
||||
int (*endelm_cb)(void *userdata, int state, const char *nspace, const char *name);
|
||||
};
|
||||
|
||||
typedef struct subdriver_s subdriver_t;
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* data for processing boolean values from UPS */
|
||||
|
||||
#define STATUS_BIT(x) (ups_status & (uint32_t)1<<x)
|
||||
#define STATUS_SET(x) (ups_status |= (uint32_t)1<<x)
|
||||
#define STATUS_CLR(x) (ups_status &= ~((uint32_t)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 */
|
||||
OVERHEAT, /* overheat */
|
||||
COMMFAULT, /* communication failure */
|
||||
INTERNALFAULT, /* internal failure */
|
||||
FANFAIL, /* fan failure */
|
||||
NOBATTERY, /* battery missing */
|
||||
BATTVOLTLO, /* battery voltage too low */
|
||||
BATTVOLTHI, /* battery voltage too high */
|
||||
CHARGERFAIL, /* battery charger failure */
|
||||
VRANGE, /* voltage out of range */
|
||||
FRANGE, /* frequency out of range */
|
||||
FUSEFAULT /* fuse fault */
|
||||
} status_bit_t;
|
||||
|
||||
extern uint32_t ups_status;
|
||||
|
||||
#endif /* NETXML_UPS_H */
|
||||
321
drivers/oneac.c
Normal file
321
drivers/oneac.c
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
/*vim ts=4*/
|
||||
|
||||
/*
|
||||
* NUT Oneac EG and ON model specific drivers for UPS units using
|
||||
* the Oneac Advanced Interface. If your UPS is equipped with the
|
||||
* Oneac Basic Interface, use the genericups driver
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright (C) 2003 by Eric Lawson <elawson@inficad.com>
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
/* 28 November 2003. Eric Lawson
|
||||
* More or less complete re-write for NUT 1.5.9
|
||||
* This was somewhat easier than trying to beat the old driver code
|
||||
* into submission
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "serial.h"
|
||||
#include "oneac.h"
|
||||
|
||||
#define DRIVER_NAME "Oneac EG/ON UPS driver"
|
||||
#define DRIVER_VERSION "0.51"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Eric Lawson <elawson@inficad.com>",
|
||||
DRV_EXPERIMENTAL,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
#define SECS 2 /*wait time*/
|
||||
#define USEC 0 /*rest of wait time*/
|
||||
|
||||
void do_battery_test(void)
|
||||
{
|
||||
char buffer[256];
|
||||
|
||||
if (getval("testtime") == NULL)
|
||||
snprintf(buffer, 3, "%s", DEFAULT_BAT_TEST_TIME);
|
||||
else {
|
||||
snprintf(buffer, 3, "%s", getval("testtime"));
|
||||
|
||||
/*the UPS wants this value to always be two characters long*/
|
||||
/*so put a zero in front of the string, if needed.... */
|
||||
if (strlen(buffer) < 2) {
|
||||
buffer[2] = '\0';
|
||||
buffer[1] = buffer[0];
|
||||
buffer[0] = '0';
|
||||
}
|
||||
}
|
||||
ser_send(upsfd,"%s%s%s",BAT_TEST_PREFIX,buffer,COMMAND_END);
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************
|
||||
*below are the commands that are called by main (part of the *
|
||||
*Above, are functions used only in this oneac driver *
|
||||
***************************************************************/
|
||||
|
||||
int instcmd(const char *cmdname, const char *extra)
|
||||
{
|
||||
if (!strcasecmp(cmdname, "test.failure.start")) {
|
||||
ser_send(upsfd,"%s%s",SIM_PWR_FAIL,COMMAND_END);
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
if (!strcasecmp(cmdname, "test.battery.start")) {
|
||||
do_battery_test();
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
if (!strcasecmp(cmdname, "test.battery.stop")) {
|
||||
ser_send(upsfd,"%s00%s",BAT_TEST_PREFIX,COMMAND_END);
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
if (!strcasecmp(cmdname, "reset.input.minmax")) {
|
||||
ser_send(upsfd,"%c%s",RESET_MIN_MAX, COMMAND_END);
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_initinfo(void)
|
||||
{
|
||||
int i;
|
||||
char buffer[256], buffer2[32];
|
||||
ser_flush_in(upsfd,"",0);
|
||||
ser_send(upsfd,"%c%s",GET_MFR,COMMAND_END);
|
||||
ser_get_line(upsfd, buffer, sizeof(buffer),ENDCHAR,IGNCHARS,SECS,USEC);
|
||||
if(strncmp(buffer,MFGR, sizeof(MFGR)))
|
||||
fatalx(EXIT_FAILURE, "Unable to connect to ONEAC UPS on %s\n",device_path);
|
||||
|
||||
dstate_setinfo("ups.mfr", "%s", buffer);
|
||||
dstate_addcmd("test.battery.start");
|
||||
dstate_addcmd("test.battery.stop");
|
||||
dstate_addcmd("test.failure.start");
|
||||
dstate_addcmd("reset.input.minmax");
|
||||
|
||||
|
||||
upsh.instcmd = instcmd;
|
||||
|
||||
/*set some stuff that shouldn't change after initialization*/
|
||||
/*this stuff is common to both the EG and ON family of UPS */
|
||||
|
||||
/*firmware revision*/
|
||||
ser_send(upsfd,"%c%s", GET_VERSION, COMMAND_END);
|
||||
ser_get_line(upsfd, buffer, sizeof(buffer),ENDCHAR,IGNCHARS,SECS,USEC);
|
||||
dstate_setinfo("ups.firmware", "%.3s",buffer);
|
||||
|
||||
/*nominal AC frequency setting --either 50 or 60*/
|
||||
ser_send(upsfd,"%c%s", GET_NOM_FREQ, COMMAND_END);
|
||||
ser_get_line(upsfd,buffer,sizeof(buffer),ENDCHAR,IGNCHARS,SECS,USEC);
|
||||
dstate_setinfo("input.frequency", "%.2s", buffer);
|
||||
|
||||
|
||||
/*UPS Model (either ON, or EG series of UPS)*/
|
||||
|
||||
ser_send(upsfd,"%c%s", GET_FAMILY,COMMAND_END);
|
||||
ser_get_line(upsfd,buffer,sizeof(buffer),ENDCHAR,IGNCHARS,SECS,USEC);
|
||||
dstate_setinfo("ups.model", "%.2s",buffer);
|
||||
printf("Found %.2s family of Oneac UPS\n", buffer);
|
||||
|
||||
if ((strncmp(buffer,FAMILY_ON,2) != 0 &&
|
||||
strncmp(buffer,FAMILY_ON_EXT,2) != 0) ||
|
||||
strncmp(buffer,FAMILY_EG,2) == 0)
|
||||
printf("Unknown family of UPS. Assuming EG capabilities.\n");
|
||||
|
||||
/*Get the actual model string for ON UPS reported as OZ family*/
|
||||
if (strncmp (dstate_getinfo("ups.model"), FAMILY_ON_EXT, 2) == 0) {
|
||||
ser_flush_in(upsfd,"",0);
|
||||
ser_send(upsfd,"%c%s",GET_ALL_EXT_2,COMMAND_END);
|
||||
ser_get_line(upsfd, buffer, sizeof(buffer),ENDCHAR,IGNCHARS,SECS,USEC);
|
||||
|
||||
/*UPS Model (full string)*/
|
||||
memset(buffer2, '\0', 32);
|
||||
strncpy(buffer2,&buffer[5], 10);
|
||||
for (i = 9; i >= 0 && buffer2[i] == ' '; --i) {
|
||||
buffer2[i] = '\0';
|
||||
}
|
||||
|
||||
dstate_setinfo("ups.model", "%s", buffer2);
|
||||
printf("Found %.10s UPS\n", buffer2);
|
||||
}
|
||||
|
||||
/*The ON (OZ) series of UPS supports more stuff than does the EG.
|
||||
*Take care of the ON (OZ) only stuff here
|
||||
*/
|
||||
if(strncmp (dstate_getinfo("ups.model"), FAMILY_ON, 2) == 0 ||
|
||||
strncmp (dstate_getinfo("ups.model"), FAMILY_ON_EXT, 2) == 0) {
|
||||
/*now set the ON specific "static" parameters*/
|
||||
|
||||
/*nominal input voltage*/
|
||||
|
||||
ser_send(upsfd,"%c%s",GET_NOM_VOLTAGE,COMMAND_END);
|
||||
ser_get_line(upsfd,buffer,sizeof(buffer),ENDCHAR,IGNCHARS,SECS,USEC);
|
||||
|
||||
switch (buffer[0]) {
|
||||
case V120AC:
|
||||
dstate_setinfo("output.voltage.nominal",
|
||||
"120");
|
||||
break;
|
||||
|
||||
case V230AC:
|
||||
dstate_setinfo("output.voltage.nominal",
|
||||
"240");
|
||||
break;
|
||||
|
||||
default:
|
||||
upslogx(LOG_INFO,"Oneac: "
|
||||
"Invalid voltage parameter from UPS");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
char buffer[256];
|
||||
int ret_value;
|
||||
|
||||
|
||||
ser_flush_in(upsfd,"",0); /*just in case*/
|
||||
ser_send (upsfd,"%c%s",GET_ALL,COMMAND_END);
|
||||
ret_value = ser_get_line(upsfd,buffer,sizeof(buffer),ENDCHAR,
|
||||
IGNCHARS,SECS,USEC);
|
||||
|
||||
upsdebugx (2,"upsrecv_updateinfo: upsrecv returned: %s\n",buffer);
|
||||
if (ret_value < 1)
|
||||
{
|
||||
ser_comm_fail("Oneac UPS Comm failure on port %s",device_path);
|
||||
dstate_datastale();
|
||||
}
|
||||
else
|
||||
{
|
||||
status_init();
|
||||
/*take care of the UPS status information*/
|
||||
switch (buffer[12]) {
|
||||
case NORMAL :
|
||||
status_set("OL");
|
||||
break;
|
||||
case ON_BAT_LOW_LINE :
|
||||
case ON_BAT_HI_LINE :
|
||||
status_set("OB");
|
||||
break;
|
||||
case LO_BAT_LOW_LINE :
|
||||
case LO_BAT_HI_LINE :
|
||||
status_set("OB LB");
|
||||
break;
|
||||
case TOO_HOT :
|
||||
status_set("OVER OB LB");
|
||||
break;
|
||||
case FIX_ME :
|
||||
dstate_setinfo("ups.test.result","UPS Internal Failure");
|
||||
break;
|
||||
case BAD_BAT :
|
||||
status_set("RB");
|
||||
break;
|
||||
default : /*cry for attention, fake a status*/
|
||||
/*Would another status be better?*/
|
||||
upslogx (LOG_ERR, "Oneac: Unknown UPS status");
|
||||
status_set("OL");
|
||||
}
|
||||
|
||||
/*take care of the reason why the UPS last transfered to battery*/
|
||||
switch (buffer[13]) {
|
||||
case XFER_BLACKOUT :
|
||||
dstate_setinfo("input.transfer.reason", "Blackout");
|
||||
break;
|
||||
case XFER_LOW_VOLT :
|
||||
dstate_setinfo("input.transfer.reason",
|
||||
"Low Input Voltage");
|
||||
break;
|
||||
case XFER_HI_VOLT :
|
||||
dstate_setinfo("input.transfer.reason",
|
||||
"High Input Voltage");
|
||||
break;
|
||||
case NO_VALUE_YET :
|
||||
dstate_setinfo("input.transfer.reason",
|
||||
"No transfer yet.");
|
||||
break;
|
||||
default :
|
||||
upslogx(LOG_INFO,"Oneac: Unknown reason for UPS battery"
|
||||
" transfer");
|
||||
}
|
||||
/* now update info for only the ON family of UPS*/
|
||||
|
||||
if (strncmp(dstate_getinfo("ups.model"), FAMILY_ON, 2) == 0) {
|
||||
dstate_setinfo("ups.load", "0%.2s",buffer+31);
|
||||
|
||||
/*battery charge*/
|
||||
if(buffer[10] == YES)
|
||||
dstate_setinfo("battery.charge", "0%.2s",buffer+33);
|
||||
else dstate_setinfo("battery.charge", "100");
|
||||
|
||||
dstate_setinfo("input.voltage", "%.3s",buffer+35);
|
||||
dstate_setinfo("input.voltage.minimum", "%.3s",buffer+38);
|
||||
dstate_setinfo("input.voltage.maximum", "%.3s",buffer+41);
|
||||
dstate_setinfo("output.voltage", "%.3s",buffer+44);
|
||||
if (buffer[47] == YES) status_set("BOOST");
|
||||
}
|
||||
status_commit();
|
||||
dstate_dataok();
|
||||
ser_comm_good();
|
||||
}
|
||||
}
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
ser_send(upsfd,"%s",SHUTDOWN);
|
||||
}
|
||||
|
||||
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
printf("\n---------\nNOTE:\n");
|
||||
printf("You must set the UPS interface card DIP switch to 9600BPS\n");
|
||||
}
|
||||
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
addvar(VAR_VALUE,"testtime",
|
||||
"Change battery test time from 2 minute default.");
|
||||
}
|
||||
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
upsfd = ser_open(device_path);
|
||||
ser_set_speed(upsfd, device_path, B9600);
|
||||
|
||||
/*get the UPS in the right frame of mind*/
|
||||
|
||||
ser_send(upsfd,"%s", COMMAND_END);
|
||||
sleep (1);
|
||||
ser_send(upsfd,"%s", COMMAND_END);
|
||||
sleep (1);
|
||||
}
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
ser_close(upsfd, device_path);
|
||||
}
|
||||
92
drivers/oneac.h
Normal file
92
drivers/oneac.h
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
Copyright (C) 2002 Eric Lawson <elawson@inficad.com>
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
/*misc stuff*/
|
||||
#define ENDCHAR '\r'
|
||||
#define IGNCHARS "\n"
|
||||
#define COMMAND_END "\r\n"
|
||||
#define DEFAULT_BAT_TEST_TIME "02"
|
||||
|
||||
/*Information requests*/
|
||||
|
||||
#define GET_ALL '%'
|
||||
#define GET_ALL_EXT_2 '^'
|
||||
#define GET_ALL_EXT_1 '&'
|
||||
#define GET_MFR 'M'
|
||||
#define GET_FAMILY 'F'
|
||||
#define GET_VERSION 'N'
|
||||
#define GET_ON_INVERTER 'G'
|
||||
#define GET_BATLOW 'K'
|
||||
#define GET_STATUS 'X'
|
||||
#define GET_LAST_XFER 'W'
|
||||
#define GET_INVERTER_RDY 'I'
|
||||
#define GET_TEST_TIME 'Q'
|
||||
#define GET_NOM_FREQ 'H'
|
||||
#define GET_NOM_VOLTAGE 'V'
|
||||
#define GET_DISPLAY_CODE 'D'
|
||||
#define GET_CONDITION_CODE 'C'
|
||||
#define GET_PERCENT_LOAD 'P'
|
||||
#define GET_PERCENT_BAT_REM 'T'
|
||||
#define GET_INPUT_LINE_VOLT 'L'
|
||||
#define GET_MIN_INPUT_VOLT 'A'
|
||||
#define GET_MAX_INPUT_VOLT 'E'
|
||||
#define GET_OUTPUT_VOLT 'S'
|
||||
#define GET_BOOSTING 'B'
|
||||
|
||||
/*Control functions*/
|
||||
#define SIM_PWR_FAIL "\x02\x15" /*^B^U 15 second battery test*/
|
||||
#define SHUTDOWN "\x0f\x06" /*^O^F (a letter O)*/
|
||||
#define RESET_MIN_MAX 'R'
|
||||
#define BAT_TEST_PREFIX "\x02" /*needs 2 more chars. minutes*/
|
||||
#define DELAYED_SHUTDOWN_PREFIX 'Z' /*needs 3 more chars. seconds */
|
||||
|
||||
#define DONT_UNDERSTAND '*'
|
||||
#define CANT_COMPLY '#'
|
||||
#define NO_VALUE_YET '.'
|
||||
|
||||
|
||||
/*responses*/
|
||||
#define MFGR "ONEAC"
|
||||
#define FAMILY_ON "ON"
|
||||
#define FAMILY_ON_EXT "OZ"
|
||||
#define FAMILY_EG "EG"
|
||||
#define YES 'Y'
|
||||
#define NO 'N'
|
||||
#define NORMAL '@'
|
||||
#define ON_BAT_LOW_LINE 'A'
|
||||
#define ON_BAT_HI_LINE 'Q'
|
||||
#define LO_BAT_LOW_LINE 'C'
|
||||
#define LO_BAT_HI_LINE 'S'
|
||||
#define TOO_HOT '`'
|
||||
#define FIX_ME 'D'
|
||||
#define BAD_BAT 'H'
|
||||
#define V230AC '2'
|
||||
#define V120AC '1'
|
||||
#define XFER_BLACKOUT 'B'
|
||||
#define XFER_LOW_VOLT 'L'
|
||||
#define XFER_HI_VOLT 'H'
|
||||
|
||||
/*front panel alarm codes*/
|
||||
#define CODE_BREAKER_OPEN "c1" /*input circuit breaker open*/
|
||||
#define CODE_BAT_FUSE_OPEN "c2" /*battery not connected. Open fuse?*/
|
||||
#define CODE_TOO_HOT "c3" /*UPS too hot*/
|
||||
#define CODE_CHARGING "c4" /*recharging battery pack*/
|
||||
#define CODE_LOW_BAT_CAP "c5" /*batteries getting too old*/
|
||||
#define CODE_OVERLOAD "c8" /*"slight" overload*/
|
||||
#define CODE_GROSS_OVLE "c9" /*gross overload 1 minute to power off*/
|
||||
#define CODE_CHRGR_FUSE_OPEN "u1" /*battery charger fuse probably open*/
|
||||
631
drivers/optiups.c
Normal file
631
drivers/optiups.c
Normal file
|
|
@ -0,0 +1,631 @@
|
|||
/* optiups.c - OptiSafe UPS (very loosely based on the nut 0.45.5 driver)
|
||||
|
||||
Copyright (C) 1999 Russell Kroll <rkroll@exploits.org>
|
||||
Copyright (C) 2006 Scott Heavner [Use my alioth acct: sheavner]
|
||||
|
||||
Support for Zinto D from ONLINE USV (only minor differences to OptiSafe UPS)
|
||||
added by Matthias Goebl <matthias.goebl@goebl.net>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
==================================================================================
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "serial.h"
|
||||
|
||||
#define DRIVER_NAME "Opti-UPS driver"
|
||||
#define DRIVER_VERSION "1.01"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Russell Kroll <rkroll@exploits.org>\n" \
|
||||
"Scott Heavner <debian@scottheavner.com>\n" \
|
||||
"Matthias Goebl <matthias.goebl@goebl.net>",
|
||||
DRV_STABLE,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
#define HELP "\n" \
|
||||
"**********************************************************" "\n" \
|
||||
"This driver has been tested only with the OPTI SAFE 420E using" "\n" \
|
||||
"the custom cable described in the NUT Opti-UPS protocol page." "\n" \
|
||||
"Seriously, it does _NOT_ work with the [windows] cable provided with" "\n" \
|
||||
"the UPS or a standard serial cable. (I don't know if OPTI makes other" "\n" \
|
||||
"UPS models that will work with another cable?) Standard linux serial" "\n" \
|
||||
"port drivers do not support the required DTR handshaking." "\n" \
|
||||
"" "\n" \
|
||||
" UPS 6 -> PC 3 This 3 wire cable pinout maps DTR to CTS." "\n" \
|
||||
" UPS 9 -> PC 2" "\n" \
|
||||
" UPS 4 -> PC 5" "\n" \
|
||||
"" "\n" \
|
||||
"This driver has also been tested with a Zinto D from Online-USV AG," "\n" \
|
||||
"using their special cable:" "\n" \
|
||||
" UPS 6 -> PC 3" "\n" \
|
||||
" UPS 9 -> PC 2" "\n" \
|
||||
" UPS 7 -> PC 5" "\n" \
|
||||
"It works even with a pl2303 usb-serial converter." "\n" \
|
||||
"**********************************************************" "\n"
|
||||
|
||||
/* See http://www.networkupstools.org/protocols/optiups.html and the end of this
|
||||
* file for more information on the cable and the OPTI-UPS serial protocol used on
|
||||
* at least the older OPTI UPS models (420E, 820ES).
|
||||
*/
|
||||
|
||||
#define ENDCHAR '\n'
|
||||
#define IGNCHARS "\r"
|
||||
|
||||
/* Our custom options available with -x */
|
||||
#define OPTI_MINPOLL "status_only"
|
||||
#define OPTI_FAKELOW "fake_lowbatt"
|
||||
#define OPTI_NOWARN_NOIMP "nowarn_noimp"
|
||||
#define OPTI_POWERUP "powerup"
|
||||
|
||||
/* All serial commands put their response in the same buffer space */
|
||||
static char _buf[256];
|
||||
|
||||
/* Model */
|
||||
static int optimodel = 0;
|
||||
enum {
|
||||
OPTIMODEL_DEFAULT = 0,
|
||||
OPTIMODEL_ZINTO =1
|
||||
};
|
||||
|
||||
|
||||
/* Status bits returned by the "AG" command */
|
||||
enum {
|
||||
OPTISBIT_NOOUTPUT = 2,
|
||||
OPTISBIT_OVERLOAD = 8,
|
||||
OPTISBIT_REPLACE_BATTERY = 16,
|
||||
OPTISBIT_ON_BATTERY_POWER = 32,
|
||||
OPTISBIT_LOW_BATTERY = 64
|
||||
};
|
||||
|
||||
/* Helper struct for the optifill() function */
|
||||
typedef struct ezfill_s {
|
||||
const char *cmd;
|
||||
const char *var;
|
||||
const float scale;
|
||||
} ezfill_t;
|
||||
|
||||
/* These can be polled right into a string usable by NUT.
|
||||
* Others such as "AG" and "BV" require some transformation of the return value */
|
||||
static ezfill_t _pollv[] = {
|
||||
{ "NV", "input.voltage" },
|
||||
{ "OL", "ups.load", 1.0 },
|
||||
{ "OV", "output.voltage" },
|
||||
{ "FF", "input.frequency", 0.1 },
|
||||
{ "BT", "ups.temperature" },
|
||||
};
|
||||
static ezfill_t _pollv_zinto[] = {
|
||||
{ "NV", "input.voltage", 2.0 },
|
||||
{ "OL", "ups.load", 1.0 },
|
||||
{ "OV", "output.voltage", 2.0 },
|
||||
{ "OF", "output.frequency", 0.1 },
|
||||
{ "NF", "input.frequency", 0.1 },
|
||||
{ "BT", "ups.temperature" },
|
||||
};
|
||||
|
||||
/* model "IO" is parsed differently in upsdrv_initinfo() */
|
||||
static ezfill_t _initv[] = {
|
||||
{ "IM", "ups.mfr" },
|
||||
{ "IZ", "ups.serial" },
|
||||
{ "IS", "ups.firmware" },
|
||||
};
|
||||
|
||||
/* All serial reads of the OPTI-UPS go through here. We always expect a CR/LF terminated
|
||||
* response. Unknown/Unimplemented commands return ^U (0x15). Actions that complete
|
||||
* successfully return ^F (0x06). */
|
||||
static inline int optireadline()
|
||||
{
|
||||
int r;
|
||||
usleep(150000);
|
||||
r = ser_get_line(upsfd, _buf, sizeof(_buf), ENDCHAR, IGNCHARS, 0, 500000 );
|
||||
_buf[sizeof(_buf)-1] = 0;
|
||||
if ( r > 0 )
|
||||
{
|
||||
if ( r < (int)sizeof(_buf) )
|
||||
_buf[r] = 0;
|
||||
if ( _buf[0] == 0x15 )
|
||||
{
|
||||
r=-2;
|
||||
upsdebugx(1, "READ: <unsupported command>");
|
||||
}
|
||||
if ( _buf[0] == 0x06 )
|
||||
{
|
||||
upsdebugx(2, "READ: <command done>");
|
||||
}
|
||||
else
|
||||
{
|
||||
upsdebugx(2, "READ: \"%s\"", _buf );
|
||||
}
|
||||
}
|
||||
else
|
||||
upsdebugx(1, "READ ERROR: %d", r );
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Send a command and read the response. Command response is in global _buf.
|
||||
* Return
|
||||
* > 0 implies success.
|
||||
* -1 serial timeout
|
||||
* -2 unknown/unimplemented command
|
||||
*/
|
||||
static inline int optiquery( const char *cmd )
|
||||
{
|
||||
upsdebugx(2, "SEND: \"%s\"", cmd );
|
||||
ser_send( upsfd, "%s", cmd );
|
||||
if ( optimodel == OPTIMODEL_ZINTO )
|
||||
ser_send( upsfd, "\r\n" );
|
||||
return optireadline();
|
||||
}
|
||||
|
||||
/* Uses the ezfill_t structure to map UPS commands to the NUT variable destinations */
|
||||
static void optifill( ezfill_t *a, int len )
|
||||
{
|
||||
int i, r;
|
||||
|
||||
/* Some things are easy to poll and store */
|
||||
for ( i=0; i<len; ++i )
|
||||
{
|
||||
if ( ( a[i].cmd == NULL ) || ( a[i].cmd[0] == 0 ) )
|
||||
continue;
|
||||
|
||||
r = optiquery( a[i].cmd );
|
||||
if ( r < 1 )
|
||||
{
|
||||
if ( r == -2 )
|
||||
{
|
||||
if ( ! testvar(OPTI_NOWARN_NOIMP) )
|
||||
upslogx( LOG_NOTICE,
|
||||
"disabling poll of unsupported var %s",
|
||||
a[i].var );
|
||||
dstate_setinfo( a[i].var, "N/A" );
|
||||
a[i].cmd = NULL;
|
||||
}
|
||||
else
|
||||
upslogx( LOG_WARNING, "can't retrieve %s", a[i].var );
|
||||
continue;
|
||||
}
|
||||
if ( a[i].scale > 1e-20 )
|
||||
{
|
||||
float f = strtol( _buf, NULL, 10 ) * a[i].scale;
|
||||
dstate_setinfo( a[i].var, "%.1f", f );
|
||||
}
|
||||
else
|
||||
{
|
||||
dstate_setinfo( a[i].var, "%s", _buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle custom (but standardized) NUT commands */
|
||||
static int instcmd(const char *cmdname, const char *extra)
|
||||
{
|
||||
if (!strcasecmp(cmdname, "test.failure.start"))
|
||||
{
|
||||
optiquery( "Ts" );
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
else if (!strcasecmp(cmdname, "load.off"))
|
||||
{
|
||||
/* You do realize this will kill power to ourself. Would probably only
|
||||
* be useful for killing power for a slave computer */
|
||||
if ( optimodel == OPTIMODEL_ZINTO )
|
||||
{
|
||||
optiquery( "Ct1" );
|
||||
optiquery( "Cs0000000" );
|
||||
sleep(2);
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
optiquery( "Ct0" );
|
||||
optiquery( "Cs00000000" );
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
else if (!strcasecmp(cmdname, "load.on"))
|
||||
{
|
||||
if ( optimodel == OPTIMODEL_ZINTO )
|
||||
{
|
||||
optiquery( "Ct1" );
|
||||
optiquery( "Cu0000000" );
|
||||
sleep(2);
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
optiquery( "Ct0" );
|
||||
optiquery( "Cu00000000" );
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
else if (!strcasecmp(cmdname, "shutdown.return"))
|
||||
{
|
||||
/* This shuts down the UPS. When the power returns to the UPS,
|
||||
* it will power back up in its default state. */
|
||||
if ( optimodel == OPTIMODEL_ZINTO )
|
||||
{
|
||||
optiquery( "Ct1" );
|
||||
optiquery( "Cu0000010" );
|
||||
optiquery( "Cs0000001" );
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
optiquery( "Ct1" );
|
||||
optiquery( "Cs00000010" );
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
else if (!strcasecmp(cmdname, "shutdown.stayoff"))
|
||||
{
|
||||
/* This actually stays off as long as the batteries hold,
|
||||
* if the line power comes back before the batteries die,
|
||||
* the UPS will never powerup its output stage!!! */
|
||||
if ( optimodel == OPTIMODEL_ZINTO )
|
||||
{
|
||||
optiquery( "Ct1" );
|
||||
optiquery( "Cs0000001" );
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
optiquery( "Ct0" );
|
||||
optiquery( "Cs00000010" );
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
else if (!strcasecmp(cmdname, "shutdown.stop"))
|
||||
{
|
||||
/* Aborts a shutdown that is couting down via the Cs command */
|
||||
optiquery( "Cs-0000001" );
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
}
|
||||
|
||||
/* Handle variable setting */
|
||||
static int setvar(const char *varname, const char *val)
|
||||
{
|
||||
int status;
|
||||
|
||||
if (sscanf(val, "%d", &status) != 1) {
|
||||
return STAT_SET_UNKNOWN;
|
||||
}
|
||||
|
||||
if (strcasecmp(varname, "outlet.1.switch") == 0) {
|
||||
status = status==1 ? 1 : 0;
|
||||
dstate_setinfo( "outlet.1.switch", "%d", status);
|
||||
optiquery(status ? "Oi11" : "Oi10");
|
||||
dstate_dataok();
|
||||
return STAT_SET_HANDLED;
|
||||
}
|
||||
|
||||
return STAT_SET_UNKNOWN;
|
||||
}
|
||||
|
||||
void upsdrv_initinfo(void)
|
||||
{
|
||||
int r;
|
||||
|
||||
/* If an Zinto Online-USV is off, switch it on first. */
|
||||
/* It sends only "2" when off, without "\r\n", and doesn't */
|
||||
/* answer other commands. Therefore without power we'll be */
|
||||
/* unable to identify the ups. */
|
||||
if ( testvar(OPTI_POWERUP) && optiquery( "AG" ) < 1 )
|
||||
{
|
||||
ser_send( upsfd, "AG\r\n" );
|
||||
r = ser_get_char(upsfd, &_buf[0], 1, 0);
|
||||
if ( r == 1 && _buf[0] == '2' )
|
||||
{
|
||||
upslogx( LOG_WARNING, "ups was off, switching it on" );
|
||||
optiquery( "Ct1" );
|
||||
optiquery( "Cu0000000" );
|
||||
/* wait for power up */
|
||||
sleep(15);
|
||||
}
|
||||
}
|
||||
|
||||
/* Autodetect an Online-USV (only Zinto D is known to work) */
|
||||
r = optiquery( "IM" );
|
||||
if ( r > 0 && !strcasecmp(_buf, "ONLINE") )
|
||||
{
|
||||
optimodel = OPTIMODEL_ZINTO;
|
||||
optiquery( "Om11" );
|
||||
optiquery( "Om21" );
|
||||
optiquery( "ON" );
|
||||
}
|
||||
|
||||
optifill( _initv, sizeof(_initv)/sizeof(_initv[0]) );
|
||||
|
||||
/* Parse out model into longer string -- is this really USEFUL??? */
|
||||
r = optiquery( "IO" );
|
||||
if ( r < 1 )
|
||||
fatal_with_errno(EXIT_FAILURE, "can't retrieve model" );
|
||||
else
|
||||
{
|
||||
switch ( _buf[r-1] )
|
||||
{
|
||||
case 'E':
|
||||
case 'P':
|
||||
case 'V':
|
||||
dstate_setinfo("ups.model", "Power%cS %s", _buf[r-1], _buf );
|
||||
break;
|
||||
default:
|
||||
dstate_setinfo("ups.model", "%s", _buf );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Parse out model into longer string */
|
||||
r = optiquery( "IM" );
|
||||
if ( r > 0 && !strcasecmp(_buf, "ONLINE") )
|
||||
{
|
||||
dstate_setinfo("ups.mfr", "ONLINE USV-Systeme AG");
|
||||
r = optiquery( "IO" );
|
||||
if ( r < 1 )
|
||||
fatal_with_errno(EXIT_FAILURE, "can't retrieve model" );
|
||||
switch ( _buf[0] )
|
||||
{
|
||||
case 'D':
|
||||
dstate_setinfo("ups.model", "Zinto %s", _buf );
|
||||
break;
|
||||
default:
|
||||
dstate_setinfo("ups.model", "%s", _buf );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dstate_addcmd("test.failure.start");
|
||||
dstate_addcmd("load.off");
|
||||
dstate_addcmd("load.on");
|
||||
if( optimodel != OPTIMODEL_ZINTO )
|
||||
dstate_addcmd("shutdown.stop");
|
||||
dstate_addcmd("shutdown.return");
|
||||
dstate_addcmd("shutdown.stayoff");
|
||||
upsh.instcmd = instcmd;
|
||||
|
||||
if ( optimodel == OPTIMODEL_ZINTO )
|
||||
{
|
||||
dstate_setinfo("outlet.desc", "%s", "Main Outlet 1+2");
|
||||
dstate_setinfo("outlet.1.desc", "%s", "Switchable Outlet 3+4");
|
||||
dstate_setinfo("outlet.id", "%d", 1);
|
||||
dstate_setinfo("outlet.1.id", "%d", 2);
|
||||
dstate_setinfo("outlet.switchable", "%d", 0);
|
||||
dstate_setinfo("outlet.1.switchable", "%d", 1);
|
||||
dstate_setinfo("outlet.1.switch", "%d", 1);
|
||||
dstate_setflags("outlet.1.switch", ST_FLAG_RW | ST_FLAG_STRING);
|
||||
dstate_setaux("outlet.1.switch", 1);
|
||||
upsh.setvar = setvar;
|
||||
}
|
||||
}
|
||||
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
int r = optiquery( "AG" );
|
||||
|
||||
/* Online-UPS send only "2" when off, without "\r\n" */
|
||||
if ( r < 1 && optimodel == OPTIMODEL_ZINTO )
|
||||
{
|
||||
ser_send( upsfd, "AG\r\n" );
|
||||
r = ser_get_char(upsfd, &_buf[0], 1, 0);
|
||||
if ( r == 1 && _buf[0] == '2' )
|
||||
{
|
||||
status_init();
|
||||
status_set("OFF");
|
||||
status_commit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( r < 1 )
|
||||
{
|
||||
upslogx(LOG_ERR, "can't retrieve ups status" );
|
||||
dstate_datastale();
|
||||
}
|
||||
else
|
||||
{
|
||||
int s = strtol( _buf, NULL, 16 );
|
||||
status_init();
|
||||
if ( s & OPTISBIT_OVERLOAD )
|
||||
status_set("OVER");
|
||||
if ( s & OPTISBIT_REPLACE_BATTERY )
|
||||
status_set("RB");
|
||||
if ( s & OPTISBIT_ON_BATTERY_POWER )
|
||||
status_set("OB");
|
||||
else
|
||||
status_set("OL");
|
||||
if ( s & OPTISBIT_NOOUTPUT )
|
||||
status_set("OFF");
|
||||
if ( s & OPTISBIT_LOW_BATTERY )
|
||||
status_set("LB");
|
||||
if ( testvar(OPTI_FAKELOW) ) /* FOR TESTING */
|
||||
status_set("LB");
|
||||
status_commit();
|
||||
dstate_dataok();
|
||||
}
|
||||
|
||||
/* Get out of here now if minimum polling is desired */
|
||||
if ( testvar(OPTI_MINPOLL) )
|
||||
return;
|
||||
|
||||
/* read some easy settings */
|
||||
if ( optimodel == OPTIMODEL_ZINTO )
|
||||
optifill( _pollv_zinto, sizeof(_pollv_zinto)/sizeof(_pollv_zinto[0]) );
|
||||
else
|
||||
optifill( _pollv, sizeof(_pollv)/sizeof(_pollv[0]) );
|
||||
|
||||
/* Battery voltage is harder */
|
||||
r = optiquery( "BV" );
|
||||
if ( r < 1 )
|
||||
upslogx( LOG_WARNING, "cannot retrieve battery voltage" );
|
||||
else
|
||||
{
|
||||
float p, v = strtol( _buf, NULL, 10 ) / 10.0;
|
||||
dstate_setinfo("battery.voltage", "%.1f", v );
|
||||
|
||||
/* battery voltage range: 10.4 - 13.0 VDC */
|
||||
p = ((v - 10.4) / 2.6) * 100.0;
|
||||
if ( p > 100.0 )
|
||||
p = 100.0;
|
||||
dstate_setinfo("battery.charge", "%.1f", p );
|
||||
}
|
||||
}
|
||||
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
/* OL: this must power cycle the load if possible */
|
||||
/* OB: the load must remain off until the power returns */
|
||||
|
||||
/* If get no response, assume on battery & battery low */
|
||||
int s = OPTISBIT_ON_BATTERY_POWER | OPTISBIT_LOW_BATTERY;
|
||||
|
||||
int r = optiquery( "AG" );
|
||||
if ( r < 1 )
|
||||
{
|
||||
upslogx(LOG_ERR, "can't retrieve ups status during shutdown" );
|
||||
}
|
||||
else
|
||||
{
|
||||
s = strtol( _buf, NULL, 16 );
|
||||
}
|
||||
|
||||
/* Turn output stage back on if power returns - but really means
|
||||
* turn off ups if on battery */
|
||||
optiquery( "Ct1" );
|
||||
|
||||
/* What happens, if the power comes back *after* reading the ups status and
|
||||
* before the shutdown command? For "Online-UPS Zinto D" *always* asking for
|
||||
* "shutdown shortly and power-up later" works perfectly, because it forces
|
||||
* a power cycle, even for the named race condition.
|
||||
* For Opti-UPS I have no information, so I wouldn't dare to change it.
|
||||
* BTW, Zinto expects only 7 digits after Cu/Cs.
|
||||
* (Matthias Goebl)
|
||||
*/
|
||||
if ( optimodel == OPTIMODEL_ZINTO )
|
||||
{
|
||||
/* On line power: Power up in 60 seconds (30 seconds after the following shutdown) */
|
||||
/* On battery: Power up when the line power returns */
|
||||
optiquery( "Cu0000060" );
|
||||
/* Shutdown in 30 seconds */
|
||||
optiquery( "Cs0000030" );
|
||||
return;
|
||||
}
|
||||
|
||||
/* Just cycling power, schedule output stage to come back on in 60 seconds */
|
||||
if ( !(s&OPTISBIT_ON_BATTERY_POWER) )
|
||||
optiquery( "Cu00000600" );
|
||||
|
||||
/* Shutdown in 8 seconds */
|
||||
optiquery( "Cs00000080" );
|
||||
}
|
||||
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
printf(HELP);
|
||||
}
|
||||
|
||||
/* list flags and values that you want to receive via -x */
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
addvar(VAR_FLAG, OPTI_MINPOLL, "Only poll for critical status variables");
|
||||
addvar(VAR_FLAG, OPTI_FAKELOW, "Fake a low battery status" );
|
||||
addvar(VAR_FLAG, OPTI_NOWARN_NOIMP, "Supress warnings of unsupported commands");
|
||||
addvar(VAR_FLAG, OPTI_POWERUP, "(Zinto D) Power-up UPS at start (cannot identify a powered-down Zinto D)");
|
||||
}
|
||||
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
upsfd = ser_open(device_path);
|
||||
ser_set_speed(upsfd, device_path, B2400);
|
||||
|
||||
/* NO PARSED COMMAND LINE VARIABLES */
|
||||
}
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
ser_close(upsfd, device_path);
|
||||
}
|
||||
|
||||
/*******************************************
|
||||
* COMMANDS THAT QUERY THE UPS
|
||||
*******************************************
|
||||
* IM - manufacturer <ViewSonic>
|
||||
* IO - model <UPS-420E>
|
||||
* IS - firmware version <V1.2B>
|
||||
* IZ - serial number <> (unsupported on 420E, returns ^U)
|
||||
*
|
||||
* BS - ??? returns <2>
|
||||
* BV - battery voltage (in deciVolts) <0140>
|
||||
* BT - battery temperature (deg C) <0033> (returns ^U on OptiUPS 420E)
|
||||
*
|
||||
* NF - input line frequency <600>
|
||||
* NV - input line voltage <116>
|
||||
*
|
||||
* OS - ?? return <3>
|
||||
* OF - output stage frequency (in 0.1 Hz) <600>
|
||||
* OV - output stage voltage <118>
|
||||
* OL - output stage load <027>
|
||||
*
|
||||
* FV - Input voltage <120>
|
||||
* FF - Input Frequency (0.1Hz) <600>
|
||||
* FO - Output volts <120>
|
||||
* FR - Output Frequency (0.1Hz) <600>
|
||||
* FA - Output VA <420>
|
||||
* FP - Ouptu power <252>
|
||||
* FU - ?? returns <2>
|
||||
* FB - ??
|
||||
* FH - High Transfer Point <144>
|
||||
* FL - Low Transfer Point <093>
|
||||
* FT - Transfer point? <121>
|
||||
*
|
||||
* AG - UPS status (bitmapped ASCII hex value) <00>
|
||||
* bit 2: 1 = <set when output stage off?>
|
||||
* bit 3: 1 = overload
|
||||
* bit 4: 1 = replace battery
|
||||
* bit 5: 1 = on battery, 0 = on line
|
||||
* bit 6: 1 = low battery
|
||||
* TR - Test results <00>
|
||||
* 00 = Unknown
|
||||
* 01 = Passed
|
||||
* 02 = Warning
|
||||
* 03 = Error
|
||||
* 04 = Aborted
|
||||
* 05 = In Progress
|
||||
* 06 = No test init
|
||||
*
|
||||
*******************************************
|
||||
* ACTIONS
|
||||
*******************************************
|
||||
*
|
||||
* Ts - Start test
|
||||
* Ct0 - set power down mode (when running on battery)
|
||||
* Ct0 = power down only ouput stage
|
||||
* Ct1 = complete power down
|
||||
* Cs00000000 - power down after delay (count in 0.1s)
|
||||
* Cs00000100 = power down in 10s
|
||||
* Cs-0000001 = cancel power down request
|
||||
* Cu00000000 - power down after delay (count in 0.1s)
|
||||
* Cu00000050 = power up in 5s
|
||||
* Cu00000000 = power up now
|
||||
*
|
||||
* CT - returns last setting passed to Ct command <0>
|
||||
*
|
||||
*******************************************
|
||||
* UNKNOWN COMMANDS (on 420e anyways)
|
||||
*******************************************
|
||||
* Fu
|
||||
* Fv
|
||||
* Ff
|
||||
* Fo
|
||||
* Fr
|
||||
* Fb
|
||||
* Fl
|
||||
* Fh
|
||||
*/
|
||||
306
drivers/powercom-hid.c
Normal file
306
drivers/powercom-hid.c
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
/* powercom-hid.c - subdriver to monitor PowerCOM USB/HID devices with NUT
|
||||
*
|
||||
* Copyright (C)
|
||||
* 2003 - 2009 Arnaud Quette <ArnaudQuette@Eaton.com>
|
||||
* 2005 - 2006 Peter Selinger <selinger@users.sourceforge.net>
|
||||
* 2008 - 2009 Arjen de Korte <adkorte-guest@alioth.debian.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "main.h" /* for getval() */
|
||||
#include "usbhid-ups.h"
|
||||
#include "powercom-hid.h"
|
||||
#include "usb-common.h"
|
||||
|
||||
#define POWERCOM_HID_VERSION "PowerCOM HID 0.2"
|
||||
/* FIXME: experimental flag to be put in upsdrv_info */
|
||||
|
||||
/* PowerCOM */
|
||||
#define POWERCOM_VENDORID 0x0d9f
|
||||
|
||||
/* USB IDs device table */
|
||||
static usb_device_id_t powercom_usb_device_table[] = {
|
||||
/* PowerCOM IMP - IMPERIAL Series */
|
||||
{ USB_DEVICE(POWERCOM_VENDORID, 0x00a2), NULL },
|
||||
/* PowerCOM SKP - Smart KING Pro (all Smart series) */
|
||||
{ USB_DEVICE(POWERCOM_VENDORID, 0x00a3), NULL },
|
||||
/* PowerCOM WOW */
|
||||
{ USB_DEVICE(POWERCOM_VENDORID, 0x00a4), NULL },
|
||||
/* PowerCOM VGD - Vanguard */
|
||||
{ USB_DEVICE(POWERCOM_VENDORID, 0x00a5), NULL },
|
||||
/* PowerCOM BNT - Black Knight Pro */
|
||||
{ USB_DEVICE(POWERCOM_VENDORID, 0x00a6), NULL },
|
||||
|
||||
/* Terminating entry */
|
||||
{ -1, -1, NULL }
|
||||
};
|
||||
|
||||
static char powercom_scratch_buf[32];
|
||||
|
||||
static char *powercom_startup_fun(double value)
|
||||
{
|
||||
uint16_t i = value;
|
||||
|
||||
snprintf(powercom_scratch_buf, sizeof(powercom_scratch_buf), "%d", 60 * (((i & 0x00FF) << 8) + (i >> 8)));
|
||||
upsdebugx(3, "%s: value = %.0f, buf = %s", __func__, value, powercom_scratch_buf);
|
||||
|
||||
return powercom_scratch_buf;
|
||||
}
|
||||
|
||||
static double powercom_startup_nuf(const char *value)
|
||||
{
|
||||
const char *s = dstate_getinfo("ups.delay.start");
|
||||
uint16_t val, command;
|
||||
|
||||
val = atoi(value ? value : s) / 60;
|
||||
command = ((val << 8) + (val >> 8));
|
||||
upsdebugx(3, "%s: value = %s, command = %04X", __func__, value, command);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static info_lkp_t powercom_startup_info[] = {
|
||||
{ 0, NULL, powercom_startup_fun, powercom_startup_nuf }
|
||||
};
|
||||
|
||||
static char *powercom_shutdown_fun(double value)
|
||||
{
|
||||
uint16_t i = value;
|
||||
|
||||
snprintf(powercom_scratch_buf, sizeof(powercom_scratch_buf), "%d", 60 * (i & 0x00FF) + (i >> 8));
|
||||
upsdebugx(3, "%s: value = %.0f, buf = %s", __func__, value, powercom_scratch_buf);
|
||||
|
||||
return powercom_scratch_buf;
|
||||
}
|
||||
|
||||
static double powercom_shutdown_nuf(const char *value)
|
||||
{
|
||||
const char *s = dstate_getinfo("ups.delay.shutdown");
|
||||
uint16_t val, command;
|
||||
|
||||
val = atoi(value ? value : s);
|
||||
command = ((val % 60) << 8) + (val / 60);
|
||||
command |= 0x4000; /* AC RESTART NORMAL ENABLE */
|
||||
upsdebugx(3, "%s: value = %s, command = %04X", __func__, value, command);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static info_lkp_t powercom_shutdown_info[] = {
|
||||
{ 0, NULL, powercom_shutdown_fun, powercom_shutdown_nuf }
|
||||
};
|
||||
|
||||
static double powercom_stayoff_nuf(const char *value)
|
||||
{
|
||||
const char *s = dstate_getinfo("ups.delay.shutdown");
|
||||
uint16_t val, command;
|
||||
|
||||
val = atoi(value ? value : s);
|
||||
command = ((val % 60) << 8) + (val / 60);
|
||||
command |= 0x8000; /* AC RESTART NORMAL DISABLE */
|
||||
upsdebugx(3, "%s: value = %s, command = %04X", __func__, value, command);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static info_lkp_t powercom_stayoff_info[] = {
|
||||
{ 0, NULL, NULL, powercom_stayoff_nuf }
|
||||
};
|
||||
|
||||
static info_lkp_t powercom_beeper_info[] = {
|
||||
{ 1, "enabled", NULL },
|
||||
{ 2, "disabled", NULL }, /* muted? */
|
||||
{ 0, NULL, NULL }
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* Vendor-specific usage table */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
/* POWERCOM usage table */
|
||||
static usage_lkp_t powercom_usage_lkp[] = {
|
||||
{ "POWERCOM1", 0x0084002f },
|
||||
{ "POWERCOM2", 0xff860060 },
|
||||
{ "POWERCOM3", 0xff860080 },
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
static usage_tables_t powercom_utab[] = {
|
||||
powercom_usage_lkp,
|
||||
hid_usage_lkp,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/* HID2NUT lookup table */
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
static hid_info_t powercom_hid2nut[] = {
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.ACPresent", NULL, NULL, 0, online_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.BatteryPresent", NULL, NULL, 0, nobattery_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.BelowRemainingCapacityLimit", NULL, NULL, 0, lowbatt_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.Charging", NULL, NULL, 0, charging_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.CommunicationLost", NULL, NULL, 0, commfault_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.Discharging", NULL, NULL, 0, discharging_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.NeedReplacement", NULL, NULL, 0, replacebatt_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.Overload", NULL, NULL, 0, overload_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.RemainingTimeLimitExpired", NULL, NULL, 0, timelimitexpired_info },
|
||||
{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.ShutdownImminent", NULL, NULL, 0, shutdownimm_info },
|
||||
/* { "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.POWERCOM3", NULL, "%.0f", 0, NULL }, */
|
||||
/* { "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.ShutdownRequested", NULL, "%.0f", 0, NULL }, */
|
||||
/* { "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.VoltageNotRegulated", NULL, "%.0f", 0, NULL }, */
|
||||
{ "BOOL", 0, 0, "UPS.PresentStatus.ACPresent", NULL, NULL, 0, online_info },
|
||||
{ "BOOL", 0, 0, "UPS.PresentStatus.BatteryPresent", NULL, NULL, 0, nobattery_info },
|
||||
{ "BOOL", 0, 0, "UPS.PresentStatus.BelowRemainingCapacityLimit", NULL, NULL, 0, lowbatt_info },
|
||||
{ "BOOL", 0, 0, "UPS.PresentStatus.Boost", NULL, NULL, 0, boost_info },
|
||||
{ "BOOL", 0, 0, "UPS.PresentStatus.Buck", NULL, NULL, 0, trim_info },
|
||||
{ "BOOL", 0, 0, "UPS.PresentStatus.Charging", NULL, NULL, 0, charging_info },
|
||||
{ "BOOL", 0, 0, "UPS.PresentStatus.CommunicationLost", NULL, NULL, 0, commfault_info },
|
||||
{ "BOOL", 0, 0, "UPS.PresentStatus.Discharging", NULL, NULL, 0, discharging_info },
|
||||
{ "BOOL", 0, 0, "UPS.PresentStatus.NeedReplacement", NULL, NULL, 0, replacebatt_info },
|
||||
{ "BOOL", 0, 0, "UPS.PresentStatus.Overload", NULL, NULL, 0, overload_info },
|
||||
{ "BOOL", 0, 0, "UPS.PresentStatus.RemainingTimeLimitExpired", NULL, NULL, 0, timelimitexpired_info },
|
||||
{ "BOOL", 0, 0, "UPS.PresentStatus.ShutdownImminent", NULL, NULL, 0, shutdownimm_info },
|
||||
/* { "BOOL", 0, 0, "UPS.PresentStatus.POWERCOM3", NULL, "%.0f", 0, NULL }, */
|
||||
/* { "BOOL", 0, 0, "UPS.PresentStatus.ShutdownRequested", NULL, "%.0f", 0, NULL }, */
|
||||
/* { "BOOL", 0, 0, "UPS.PresentStatus.Tested", NULL, "%.0f", 0, NULL }, */
|
||||
/* { "BOOL", 0, 0, "UPS.PresentStatus.VoltageNotRegulated", NULL, "%.0f", 0, NULL }, */
|
||||
|
||||
/*
|
||||
* According to the HID PDC specifications, the below values should report battery.voltage(.nominal)
|
||||
* PowerCOM duplicates the output.voltage(.nominal) here, so we ignore them
|
||||
* { "battery.voltage", 0, 0, "UPS.PowerSummary.Voltage", NULL, "%.2f", 0, NULL },
|
||||
* { "battery.voltage", 0, 0, "UPS.Battery.Voltage", NULL, "%.2f", 0, NULL },
|
||||
* { "battery.voltage.nominal", 0, 0, "UPS.PowerSummary.ConfigVoltage", NULL, "%.0f", HU_FLAG_STATIC, NULL },
|
||||
* { "battery.voltage.nominal", 0, 0, "UPS.Battery.ConfigVoltage", NULL, "%.0f", HU_FLAG_STATIC, NULL },
|
||||
*/
|
||||
{ "battery.charge", 0, 0, "UPS.PowerSummary.RemainingCapacity", NULL, "%.0f", 0, NULL },
|
||||
{ "battery.charge", 0, 0, "UPS.Battery.RemainingCapacity", NULL, "%.0f", 0, NULL },
|
||||
{ "battery.charge.low", 0, 0, "UPS.PowerSummary.RemainingCapacityLimit", NULL, "%.0f", 0, NULL },
|
||||
{ "battery.charge.warning", 0, 0, "UPS.PowerSummary.WarningCapacityLimit", NULL, "%.0f", 0, NULL },
|
||||
{ "battery.runtime", 0, 0, "UPS.PowerSummary.RunTimeToEmpty", NULL, "%.0f", 0, NULL },
|
||||
{ "battery.date", 0, 0, "UPS.Battery.ManufacturerDate", NULL, "%s", HU_FLAG_STATIC, date_conversion },
|
||||
{ "battery.type", 0, 0, "UPS.PowerSummary.iDeviceChemistry", NULL, "%s", HU_FLAG_STATIC, stringid_conversion },
|
||||
/* { "unmapped.ups.battery.delaybeforestartup", 0, 0, "UPS.Battery.DelayBeforeStartup", NULL, "%.0f", 0, NULL }, */
|
||||
/* { "unmapped.ups.battery.initialized", 0, 0, "UPS.Battery.Initialized", NULL, "%.0f", 0, NULL }, */
|
||||
|
||||
{ "ups.beeper.status", 0, 0, "UPS.PowerSummary.AudibleAlarmControl", NULL, "%s", 0, powercom_beeper_info },
|
||||
{ "ups.beeper.status", 0, 0, "UPS.AudibleAlarmControl", NULL, "%s", 0, powercom_beeper_info },
|
||||
{ "ups.load", 0, 0, "UPS.Output.PercentLoad", NULL, "%.0f", 0, NULL },
|
||||
{ "ups.date", 0, 0, "UPS.PowerSummary.ManufacturerDate", NULL, "%s", HU_FLAG_STATIC, date_conversion },
|
||||
{ "ups.test.result", 0, 0, "UPS.Battery.Test", NULL, "%s", 0, test_read_info },
|
||||
/* { "unmapped.ups.powersummary.imanufacturer", 0, 0, "UPS.PowerSummary.iManufacturer", NULL, "%s", HU_FLAG_STATIC, stringid_conversion }, */
|
||||
/* { "unmapped.ups.powersummary.iproduct", 0, 0, "UPS.PowerSummary.iProduct", NULL, "%s", HU_FLAG_STATIC, stringid_conversion }, */
|
||||
/* { "unmapped.ups.powersummary.iserialnumber", 0, 0, "UPS.PowerSummary.iSerialNumber", NULL, "%s", HU_FLAG_STATIC, stringid_conversion }, */
|
||||
/* { "unmapped.ups.iname", 0, 0, "UPS.iName", NULL, "%s", HU_FLAG_STATIC, stringid_conversion }, */
|
||||
/* { "unmapped.ups.powersummary.ioeminformation", 0, 0, "UPS.PowerSummary.iOEMInformation", NULL, "%s", HU_FLAG_STATIC, stringid_conversion }, */
|
||||
|
||||
/* The implementation of the HID path UPS.PowerSummary.DelayBeforeStartup is unconventional:
|
||||
* Read:
|
||||
* Byte 7, byte 8 (min)
|
||||
* Write:
|
||||
* Command 4, high byte min, low byte min
|
||||
*/
|
||||
{ "ups.delay.start", ST_FLAG_RW | ST_FLAG_STRING, 8, "UPS.PowerSummary.DelayBeforeStartup", NULL, "60", HU_FLAG_ABSENT, NULL },
|
||||
{ "ups.timer.start", 0, 0, "UPS.PowerSummary.DelayBeforeStartup", NULL, "%.0f", 0, powercom_startup_info },
|
||||
|
||||
/* The implementation of the HID path UPS.PowerSummary.DelayBeforeShutdown is unconventional:
|
||||
* Read:
|
||||
* Byte 13, Byte 14 (min, sec)
|
||||
* Write:
|
||||
* If Byte(sec), bit7=0 and bit6=0 Then
|
||||
* If Byte 9, bit0=1 Then command 185, 188, min, sec (OL -> shutdown.return)
|
||||
* If Byte 9, bit0=0 Then command 186, 188, min, sec (OB -> shutdown.stayoff)
|
||||
* If Byte(sec), bit7=0 and bit6=1
|
||||
* Then command 185, 188, min, sec (shutdown.return)
|
||||
* If Byte(sec), bit7=1 and bit6=0 Then
|
||||
* Then command 186, 188, min, sec (shutdown.stayoff)
|
||||
* If Byte(sec), bit7=1 and bit6=1 Then
|
||||
* No actions
|
||||
*/
|
||||
{ "ups.delay.shutdown", ST_FLAG_RW | ST_FLAG_STRING, 8, "UPS.PowerSummary.DelayBeforeShutdown", NULL, DEFAULT_OFFDELAY, HU_FLAG_ABSENT, NULL },
|
||||
{ "ups.timer.shutdown", 0, 0, "UPS.PowerSummary.DelayBeforeShutdown", NULL, "%.0f", HU_FLAG_QUICK_POLL, powercom_shutdown_info },
|
||||
|
||||
{ "input.voltage", 0, 0, "UPS.Input.Voltage", NULL, "%.1f", 0, NULL },
|
||||
{ "input.voltage.nominal", 0, 0, "UPS.Input.ConfigVoltage", NULL, "%.0f", HU_FLAG_STATIC, NULL },
|
||||
{ "input.frequency", 0, 0, "UPS.Input.Frequency", NULL, "%.1f", 0, NULL },
|
||||
|
||||
{ "output.voltage", 0, 0, "UPS.Output.Voltage", NULL, "%.1f", 0, NULL },
|
||||
{ "output.voltage.nominal", 0, 0, "UPS.Output.ConfigVoltage", NULL, "%.0f", HU_FLAG_STATIC, NULL },
|
||||
{ "output.frequency", 0, 0, "UPS.Output.Frequency", NULL, "%.1f", 0, NULL },
|
||||
|
||||
/* { "unmapped.ups.powercom1", 0, 0, "UPS.POWERCOM1", NULL, "%.0f", 0, NULL }, broken pipe */
|
||||
/* { "unmapped.ups.powercom2", 0, 0, "UPS.POWERCOM2", NULL, "%.0f", 0, NULL }, broken pipe */
|
||||
/* { "unmapped.ups.powersummary.rechargeable", 0, 0, "UPS.PowerSummary.Rechargeable", NULL, "%.0f", 0, NULL }, */
|
||||
/* { "unmapped.ups.shutdownimminent", 0, 0, "UPS.ShutdownImminent", NULL, "%.0f", 0, NULL }, */
|
||||
|
||||
/* instcmds */
|
||||
{ "beeper.toggle", 0, 0, "UPS.PowerSummary.AudibleAlarmControl", NULL, "1", HU_TYPE_CMD, NULL },
|
||||
{ "test.battery.start.quick", 0, 0, "UPS.Battery.Test", NULL, "1", HU_TYPE_CMD, NULL },
|
||||
{ "load.on.delay", 0, 0, "UPS.PowerSummary.DelayBeforeStartup", NULL, NULL, HU_TYPE_CMD, powercom_startup_info },
|
||||
{ "shutdown.return", 0, 0, "UPS.PowerSummary.DelayBeforeShutdown", NULL, NULL, HU_TYPE_CMD, powercom_shutdown_info },
|
||||
{ "shutdown.stayoff", 0, 0, "UPS.PowerSummary.DelayBeforeShutdown", NULL, NULL, HU_TYPE_CMD, powercom_stayoff_info },
|
||||
|
||||
/* end of structure. */
|
||||
{ NULL, 0, 0, NULL, NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
static char *powercom_format_model(HIDDevice_t *hd) {
|
||||
return hd->Product;
|
||||
}
|
||||
|
||||
static char *powercom_format_mfr(HIDDevice_t *hd) {
|
||||
return hd->Vendor ? hd->Vendor : "PowerCOM";
|
||||
}
|
||||
|
||||
static char *powercom_format_serial(HIDDevice_t *hd) {
|
||||
return hd->Serial;
|
||||
}
|
||||
|
||||
/* this function allows the subdriver to "claim" a device: return 1 if
|
||||
* the device is supported by this subdriver, else 0. */
|
||||
static int powercom_claim(HIDDevice_t *hd)
|
||||
{
|
||||
int status = is_usb_device_supported(powercom_usb_device_table, hd->VendorID, hd->ProductID);
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case POSSIBLY_SUPPORTED:
|
||||
/* by default, reject, unless the productid option is given */
|
||||
if (getval("productid")) {
|
||||
return 1;
|
||||
}
|
||||
possibly_supported("PowerCOM", hd);
|
||||
return 0;
|
||||
|
||||
case SUPPORTED:
|
||||
return 1;
|
||||
|
||||
case NOT_SUPPORTED:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
subdriver_t powercom_subdriver = {
|
||||
POWERCOM_HID_VERSION,
|
||||
powercom_claim,
|
||||
powercom_utab,
|
||||
powercom_hid2nut,
|
||||
powercom_format_model,
|
||||
powercom_format_mfr,
|
||||
powercom_format_serial,
|
||||
};
|
||||
30
drivers/powercom-hid.h
Normal file
30
drivers/powercom-hid.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/* powercom-hid.h - subdriver to monitor PowerCOM USB/HID devices with NUT
|
||||
*
|
||||
* Copyright (C)
|
||||
* 2003 - 2009 Arnaud Quette <ArnaudQuette@Eaton.com>
|
||||
* 2005 - 2006 Peter Selinger <selinger@users.sourceforge.net>
|
||||
* 2008 - 2009 Arjen de Korte <adkorte-guest@alioth.debian.org>
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef POWERCOM_HID_H
|
||||
#define POWERCOM_HID_H
|
||||
|
||||
#include "usbhid-ups.h"
|
||||
|
||||
extern subdriver_t powercom_subdriver;
|
||||
|
||||
#endif /* POWERCOM_HID_H */
|
||||
985
drivers/powercom.c
Normal file
985
drivers/powercom.c
Normal file
|
|
@ -0,0 +1,985 @@
|
|||
/*
|
||||
* powercom.c - model specific routines for following units:
|
||||
* -Trust 425/625
|
||||
* -Powercom
|
||||
* -Advice Partner/King PR750
|
||||
* See http://www.advice.co.il/product/inter/ups.html for its specifications.
|
||||
* This model is based on PowerCom (www.powercom.com) models.
|
||||
* -Socomec Sicon Egys 420
|
||||
*
|
||||
* $Id: powercom.c 2336 2010-02-11 20:16:43Z adkorte-guest $
|
||||
*
|
||||
* Copyrights:
|
||||
* (C) 2002 Simon Rozman <simon@rozman.net>
|
||||
* (C) 1999 Peter Bieringer <pb@bieringer.de>
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* rev 0.7: Alexey Sidorov <alexsid@altlinux.org>
|
||||
* - add Powercom's Black Knight Pro model support ( BNT-400/500/600/800/801/1000/1200/1500/2000AP 220-240V )
|
||||
*
|
||||
* rev 0.8: Alexey Sidorov <alexsid@altlinux.org>
|
||||
* - add Powercom's King Pro model support ( KIN-425/525/625/800/1000/1200/1500/1600/2200/3000/5000AP[-RM] 100-120,200-240 V)
|
||||
*
|
||||
* rev 0.9: Alexey Sidorov <alexsid@altlinux.org>
|
||||
* - add Powercom's Imperial model support ( IMP-xxxAP, IMD-xxxAP )
|
||||
*
|
||||
* rev 0.10: Alexey Sidorov <alexsid@altlinux.org>
|
||||
* - fix wrong detection KIN-2200AP
|
||||
* - use ser_set_dtr/ser_set_rts
|
||||
*
|
||||
* rev 0.11: Alexey Sidorov <alexsid@altlinux.org>
|
||||
* - move variables from .h to .c file (thanks Michael Tokarev for bugreport)
|
||||
* - fix string comparison (thanks Michael Tokarev for bugreport & Charles Lepple for patch)
|
||||
* - added BNT-other, for BNT 100-120V models (I havn't specs for it)
|
||||
*
|
||||
* Tested on: BNT-1200AP
|
||||
*
|
||||
* Known bugs:
|
||||
* - strange battery level on BNT1200AP in online mode( & may be on other models)
|
||||
* - i don't know how connect to IMP|IMD USB
|
||||
* - i havn't specs for BNT 100-120V models. Add BNT-other type for it
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "serial.h"
|
||||
#include "powercom.h"
|
||||
#include "math.h"
|
||||
|
||||
#define DRIVER_NAME "PowerCom protocol UPS driver"
|
||||
#define DRIVER_VERSION "0.12"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Simon Rozman <simon@rozman.net>\n" \
|
||||
"Peter Bieringer <pb@bieringer.de>\n" \
|
||||
"Alexey Sidorov <alexsid@altlinux.org>",
|
||||
DRV_STABLE,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
#define NUM_OF_SUBTYPES (sizeof (types) / sizeof (*types))
|
||||
|
||||
/* general constants */
|
||||
enum general {
|
||||
MAX_NUM_OF_BYTES_FROM_UPS = 16
|
||||
};
|
||||
|
||||
/* variables used by module */
|
||||
static unsigned char raw_data[MAX_NUM_OF_BYTES_FROM_UPS]; /* raw data reveived from UPS */
|
||||
static unsigned int linevoltage = 230U; /* line voltage, can be defined via command line option */
|
||||
static const char *manufacturer = "PowerCom";
|
||||
static const char *modelname = "Unknown";
|
||||
static const char *serialnumber = "Unknown";
|
||||
static unsigned int type = 0;
|
||||
|
||||
|
||||
/* forward declaration of functions used to setup flow control */
|
||||
static void dtr0rts1 (void);
|
||||
static void no_flow_control (void);
|
||||
|
||||
/* struct defining types */
|
||||
static struct type types[] = {
|
||||
{
|
||||
"Trust",
|
||||
11,
|
||||
{ "dtr0rts1", dtr0rts1 },
|
||||
{ { 5U, 0U }, { 7U, 0U }, { 8U, 0U } },
|
||||
{ { 0U, 10U }, 'n' },
|
||||
{ 0.00020997, 0.00020928 },
|
||||
{ 6.1343, -0.3808, 4.3110, 0.1811 },
|
||||
{ 5.0000, 0.3268, -825.00, 4.5639, -835.82 },
|
||||
{ 1.9216, -0.0977, 0.9545, 0.0000 },
|
||||
},
|
||||
{
|
||||
"Egys",
|
||||
16,
|
||||
{ "no_flow_control", no_flow_control },
|
||||
{ { 5U, 0x80U }, { 7U, 0U }, { 8U, 0U } },
|
||||
{ { 0U, 10U }, 'n' },
|
||||
{ 0.00020997, 0.00020928 },
|
||||
{ 6.1343, -0.3808, 1.3333, 0.6667 },
|
||||
{ 5.0000, 0.3268, -825.00, 2.2105, -355.37 },
|
||||
{ 1.9216, -0.0977, 0.9545, 0.0000 },
|
||||
},
|
||||
{
|
||||
"KP625AP",
|
||||
16,
|
||||
{ "dtr0rts1", dtr0rts1 },
|
||||
{ { 5U, 0x80U }, { 7U, 0U }, { 8U, 0U } },
|
||||
{ { 0U, 10U }, 'n' },
|
||||
{ 0.00020997, 0.00020928 },
|
||||
{ 6.1343, -0.3808, 4.3110, 0.1811 },
|
||||
{ 5.0000, 0.3268, -825.00, 4.5639, -835.82 },
|
||||
{ 1.9216, -0.0977, 0.9545, 0.0000 },
|
||||
},
|
||||
{
|
||||
"IMP",
|
||||
16,
|
||||
{ "no_flow_control", no_flow_control },
|
||||
{ { 5U, 0xFFU }, { 7U, 0U }, { 8U, 0U } },
|
||||
{ { 1U, 30U }, 'y' },
|
||||
{ 0.00020997, 0.00020928 },
|
||||
{ 6.1343, -0.3808, 4.3110, 0.1811 },
|
||||
{ 5.0000, 0.3268, -825.00, 4.5639, -835.82 },
|
||||
{ 1.9216, -0.0977, 0.9545, 0.0000 },
|
||||
},
|
||||
{
|
||||
"KIN",
|
||||
16,
|
||||
{ "no_flow_control", no_flow_control },
|
||||
{ { 11U, 0x4bU }, { 8U, 0U }, { 8U, 0U } },
|
||||
{ { 1U, 30U }, 'y' },
|
||||
{ 0.00020997, 0.0 },
|
||||
{ 6.1343, -0.3808, 1.075, 0.1811 },
|
||||
{ 5.0000, 0.3268, -825.00, 0.46511, 0 },
|
||||
{ 1.9216, -0.0977, 0.82857, 0.0000 },
|
||||
},
|
||||
{
|
||||
"BNT",
|
||||
16,
|
||||
{ "no_flow_control", no_flow_control },
|
||||
{ { 11U, 0x42U }, { 8U, 0U }, { 8U, 0U } },
|
||||
{ { 1U, 30U }, 'y' },
|
||||
{ 0.00020803, 0.0 },
|
||||
{ 1.4474, 0.0, 0.8594, 0.0 },
|
||||
{ 5.0000, 0.3268, -825.00, 0.46511, 0 },
|
||||
{ 1.9216, -0.0977, 0.82857, 0.0000 },
|
||||
},
|
||||
{
|
||||
"BNT-other",
|
||||
16,
|
||||
{ "no_flow_control", no_flow_control },
|
||||
{ { 8U, 0U }, { 8U, 0U }, { 8U, 0U } },
|
||||
{ { 1U, 30U }, 'y' },
|
||||
{ 0.00020803, 0.0 },
|
||||
{ 1.4474, 0.0, 0.8594, 0.0 },
|
||||
{ 5.0000, 0.3268, -825.00, 0.46511, 0 },
|
||||
{ 1.9216, -0.0977, 0.82857, 0.0000 },
|
||||
},
|
||||
};
|
||||
|
||||
/* values for sending to UPS */
|
||||
enum commands {
|
||||
SEND_DATA = '\x01',
|
||||
BATTERY_TEST = '\x03',
|
||||
WAKEUP_TIME = '\x04',
|
||||
RESTART = '\xb9',
|
||||
SHUTDOWN = '\xba',
|
||||
COUNTER = '\xbc'
|
||||
};
|
||||
|
||||
/* location of data in received string */
|
||||
enum data {
|
||||
UPS_LOAD = 0U,
|
||||
BATTERY_CHARGE = 1U,
|
||||
INPUT_VOLTAGE = 2U,
|
||||
OUTPUT_VOLTAGE = 3U,
|
||||
INPUT_FREQUENCY = 4U,
|
||||
UPSVERSION = 5U,
|
||||
OUTPUT_FREQUENCY = 6U,
|
||||
STATUS_A = 9U,
|
||||
STATUS_B = 10U,
|
||||
MODELNAME = 11U,
|
||||
MODELNUMBER = 12U
|
||||
};
|
||||
|
||||
/* status bits */
|
||||
enum status {
|
||||
SUMMARY = 0U,
|
||||
MAINS_FAILURE = 1U,
|
||||
ONLINE = 1U,
|
||||
FAULT = 1U,
|
||||
LOW_BAT = 2U,
|
||||
BAD_BAT = 2U,
|
||||
TEST = 4U,
|
||||
AVR_ON = 8U,
|
||||
AVR_MODE = 16U,
|
||||
SD_COUNTER = 16U,
|
||||
OVERLOAD = 32U,
|
||||
SHED_COUNTER = 32U,
|
||||
DIS_NOLOAD = 64U,
|
||||
SD_DISPLAY = 128U,
|
||||
OFF = 128U
|
||||
};
|
||||
|
||||
unsigned int voltages[]={100,110,115,120,0,0,0,200,220,230,240};
|
||||
unsigned int BNTmodels[]={0,400,500,600,800,801,1000,1200,1500,2000};
|
||||
unsigned int KINmodels[]={0,425,500,525,625,800,1000,1200,1500,1600,2200,2200,2500,3000,5000};
|
||||
unsigned int IMPmodels[]={0,425,525,625,825,1025,1200,1500,2000};
|
||||
|
||||
/*
|
||||
* local used functions
|
||||
*/
|
||||
|
||||
static void shutdown_halt(void)
|
||||
{
|
||||
ser_send_char (upsfd, SHUTDOWN);
|
||||
if (types[type].shutdown_arguments.minutesShouldBeUsed != 'n')
|
||||
ser_send_char (upsfd, types[type].shutdown_arguments.delay[0]);
|
||||
ser_send_char (upsfd, types[type].shutdown_arguments.delay[1]);
|
||||
upslogx(LOG_INFO, "Shutdown (stayoff) initiated.");
|
||||
exit (0);
|
||||
}
|
||||
|
||||
static void shutdown_ret(void)
|
||||
{
|
||||
ser_send_char (upsfd, RESTART);
|
||||
ser_send_char (upsfd, COUNTER);
|
||||
if (types[type].shutdown_arguments.minutesShouldBeUsed != 'n')
|
||||
ser_send_char (upsfd, types[type].shutdown_arguments.delay[0]);
|
||||
ser_send_char (upsfd, types[type].shutdown_arguments.delay[1]);
|
||||
upslogx(LOG_INFO, "Shutdown (return) initiated.");
|
||||
|
||||
exit (0);
|
||||
}
|
||||
|
||||
/* registered instant commands */
|
||||
static int instcmd (const char *cmdname, const char *extra)
|
||||
{
|
||||
if (!strcasecmp(cmdname, "test.battery.start")) {
|
||||
ser_send_char (upsfd, BATTERY_TEST);
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
if (!strcasecmp(cmdname, "shutdown.return")) {
|
||||
shutdown_ret();
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
if (!strcasecmp(cmdname, "shutdown.stayoff")) {
|
||||
shutdown_halt();
|
||||
return STAT_INSTCMD_HANDLED;
|
||||
}
|
||||
|
||||
upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
}
|
||||
|
||||
/* set DTR and RTS lines on a serial port to supply a passive
|
||||
* serial interface: DTR to 0 (-V), RTS to 1 (+V)
|
||||
*/
|
||||
static void dtr0rts1 (void)
|
||||
{
|
||||
ser_set_dtr(upsfd, 0);
|
||||
ser_set_rts(upsfd, 1);
|
||||
upsdebugx(2, "DTR => 0, RTS => 1");
|
||||
}
|
||||
|
||||
/* clear any flow control */
|
||||
static void no_flow_control (void)
|
||||
{
|
||||
struct termios tio;
|
||||
|
||||
tcgetattr (upsfd, &tio);
|
||||
|
||||
tio.c_iflag &= ~ (IXON | IXOFF);
|
||||
tio.c_cc[VSTART] = _POSIX_VDISABLE;
|
||||
tio.c_cc[VSTOP] = _POSIX_VDISABLE;
|
||||
|
||||
upsdebugx(2, "Flow control disable");
|
||||
|
||||
/* disable any flow control */
|
||||
tcsetattr(upsfd, TCSANOW, &tio);
|
||||
}
|
||||
|
||||
/* sane check for returned buffer */
|
||||
static int validate_raw_data (void)
|
||||
{
|
||||
int i = 0,
|
||||
num_of_tests =
|
||||
sizeof types[0].validation / sizeof types[0].validation[0];
|
||||
|
||||
for (i = 0;
|
||||
i < num_of_tests &&
|
||||
raw_data[
|
||||
types[type].validation[i].index_of_byte] ==
|
||||
types[type].validation[i].required_value;
|
||||
i++) ;
|
||||
return (i < num_of_tests) ? 1 : 0;
|
||||
}
|
||||
|
||||
/* get info from ups */
|
||||
static int ups_getinfo(void)
|
||||
{
|
||||
int i, c;
|
||||
|
||||
/* send trigger char to UPS */
|
||||
if (ser_send_char (upsfd, SEND_DATA) != 1) {
|
||||
upslogx(LOG_NOTICE, "writing error");
|
||||
dstate_datastale();
|
||||
return 0;
|
||||
} else {
|
||||
upsdebugx(5, "Num of bytes requested for reading from UPS: %d", types[type].num_of_bytes_from_ups);
|
||||
|
||||
c = ser_get_buf_len(upsfd, raw_data,
|
||||
types[type].num_of_bytes_from_ups, 3, 0);
|
||||
|
||||
if (c != types[type].num_of_bytes_from_ups) {
|
||||
upslogx(LOG_NOTICE, "data receiving error (%d instead of %d bytes)", c, types[type].num_of_bytes_from_ups);
|
||||
dstate_datastale();
|
||||
return 0;
|
||||
} else
|
||||
upsdebugx(5, "Num of bytes received from UPS: %d", c);
|
||||
|
||||
};
|
||||
|
||||
/* optional dump of raw data */
|
||||
if (nut_debug_level > 4) {
|
||||
printf("Raw data from UPS:\n");
|
||||
for (i = 0; i < types[type].num_of_bytes_from_ups; i++) {
|
||||
printf("%2d 0x%02x (%c)\n", i, raw_data[i], raw_data[i]>=0x20 ? raw_data[i] : ' ');
|
||||
};
|
||||
};
|
||||
|
||||
/* validate raw data for correctness */
|
||||
if (validate_raw_data() != 0) {
|
||||
upslogx(LOG_NOTICE, "data receiving error (validation check)");
|
||||
dstate_datastale();
|
||||
return 0;
|
||||
};
|
||||
return 1;
|
||||
}
|
||||
|
||||
static float input_voltage(void)
|
||||
{
|
||||
unsigned int model;
|
||||
float tmp=0.0;
|
||||
|
||||
if ( !strcmp(types[type].name, "BNT") && raw_data[MODELNUMBER]%16 > 7 ) {
|
||||
tmp=2.2*raw_data[INPUT_VOLTAGE]-24;
|
||||
} else if ( !strcmp(types[type].name, "KIN")) {
|
||||
model=KINmodels[raw_data[MODELNUMBER]/16];
|
||||
if (model<=625){
|
||||
tmp=1.79*raw_data[INPUT_VOLTAGE]+3.35;
|
||||
} else if (model<2000){
|
||||
tmp=1.61*raw_data[INPUT_VOLTAGE];
|
||||
} else {
|
||||
tmp=1.625*raw_data[INPUT_VOLTAGE];
|
||||
}
|
||||
} else if ( !strcmp(types[type].name, "IMP")) {
|
||||
tmp=raw_data[INPUT_VOLTAGE]*2.0;
|
||||
} else {
|
||||
tmp=linevoltage >= 220 ?
|
||||
types[type].voltage[0] * raw_data[INPUT_VOLTAGE] + types[type].voltage[1] :
|
||||
types[type].voltage[2] * raw_data[INPUT_VOLTAGE] + types[type].voltage[3];
|
||||
}
|
||||
if (tmp<0) tmp=0.0;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
static float output_voltage(void)
|
||||
{
|
||||
float tmp,rdatax,rdatay,rdataz,boostdata;
|
||||
unsigned int statINV = 0,statAVR = 0,statAVRMode = 0,model,t;
|
||||
static float datax[]={0,1.0,1.0,1.0,1.0,1.89,1.89,1.89,0.127,0.127,1.89,1.89,1.89,0.256};
|
||||
static float datay[]={0,1.73,1.74,1.74,1.77,0.9,0.9,0.9,13.204,13.204,0.88,0.88,0.88,6.645};
|
||||
static float dataz[]={0,1.15,0.9,0.9,0.75,1.1,1.1,1.1,0.8,0.8,0.86,0.86,0.86,0.7};
|
||||
|
||||
if ( !strcmp(types[type].name, "BNT") || !strcmp(types[type].name, "KIN")) {
|
||||
statINV=raw_data[STATUS_A] & ONLINE;
|
||||
statAVR=raw_data[STATUS_A] & AVR_ON;
|
||||
statAVRMode=raw_data[STATUS_A] & AVR_MODE;
|
||||
}
|
||||
if ( !strcmp(types[type].name, "BNT") && raw_data[MODELNUMBER]%16 > 7 ) {
|
||||
if (statINV==0) {
|
||||
if (statAVR==0){
|
||||
tmp=2.2*raw_data[OUTPUT_VOLTAGE]-24;
|
||||
} else {
|
||||
if (statAVRMode > 0)
|
||||
tmp=(2.2*raw_data[OUTPUT_VOLTAGE]-24)*31/27;
|
||||
else
|
||||
tmp=(2.22*raw_data[OUTPUT_VOLTAGE]-24)*27/31;
|
||||
}
|
||||
} else {
|
||||
t=raw_data[OUTPUT_FREQUENCY]/2;
|
||||
tmp=(1.965*raw_data[15])*(1.965*raw_data[15])*(t-raw_data[OUTPUT_VOLTAGE])/t;
|
||||
if (tmp>0)
|
||||
tmp=sqrt(tmp);
|
||||
else
|
||||
tmp=0.0;
|
||||
}
|
||||
} else if ( !strcmp(types[type].name, "KIN")) {
|
||||
model=KINmodels[raw_data[MODELNUMBER]/16];
|
||||
if (statINV==0) {
|
||||
if (statAVR==0) {
|
||||
if (model<=625)
|
||||
tmp=1.79*raw_data[OUTPUT_VOLTAGE]+3.35;
|
||||
else if (model<2000)
|
||||
tmp=1.61*raw_data[OUTPUT_VOLTAGE];
|
||||
else
|
||||
tmp=1.625*raw_data[OUTPUT_VOLTAGE];
|
||||
} else {
|
||||
if (statAVRMode > 0){
|
||||
if (model<=525)
|
||||
tmp=2.07*raw_data[OUTPUT_VOLTAGE];
|
||||
else if (model==625)
|
||||
tmp=2.07*raw_data[OUTPUT_VOLTAGE]+5;
|
||||
else if (model<2000)
|
||||
tmp=1.87*raw_data[OUTPUT_VOLTAGE];
|
||||
else
|
||||
tmp=1.87*raw_data[OUTPUT_VOLTAGE];
|
||||
} else {
|
||||
if (model<=625)
|
||||
tmp=1.571*raw_data[OUTPUT_VOLTAGE];
|
||||
else if (model<2000)
|
||||
tmp=1.37*raw_data[OUTPUT_VOLTAGE];
|
||||
else
|
||||
tmp=1.4*raw_data[OUTPUT_VOLTAGE];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rdatax=datax[raw_data[MODELNUMBER]/16];
|
||||
rdatay=datay[raw_data[MODELNUMBER]/16];
|
||||
rdataz=dataz[raw_data[MODELNUMBER]/16];
|
||||
boostdata=1.0+statAVR*20.0/135.0;
|
||||
t=raw_data[OUTPUT_FREQUENCY]/2;
|
||||
tmp=0;
|
||||
if (model>625){
|
||||
tmp=(raw_data[BATTERY_CHARGE]*rdatax)*(raw_data[BATTERY_CHARGE]*rdatax)*
|
||||
(t-raw_data[OUTPUT_VOLTAGE])/t;
|
||||
if (tmp>0)
|
||||
tmp=sqrt(tmp)*rdatay*boostdata-raw_data[UPS_LOAD]*rdataz*boostdata;
|
||||
} else {
|
||||
tmp=(raw_data[BATTERY_CHARGE]*rdatax-raw_data[UPS_LOAD]*rdataz)*
|
||||
(raw_data[BATTERY_CHARGE]*rdatax-raw_data[UPS_LOAD]*rdataz)*
|
||||
(t-raw_data[OUTPUT_VOLTAGE])/t;
|
||||
if (tmp>0)
|
||||
tmp=sqrt(tmp)*rdatay;
|
||||
}
|
||||
}
|
||||
} else if ( !strcmp(types[type].name, "IMP")) {
|
||||
tmp=raw_data[OUTPUT_VOLTAGE]*2.0;
|
||||
} else {
|
||||
tmp= linevoltage >= 220 ?
|
||||
types[type].voltage[0] * raw_data[OUTPUT_VOLTAGE] +
|
||||
types[type].voltage[1] :
|
||||
types[type].voltage[2] * raw_data[OUTPUT_VOLTAGE] +
|
||||
types[type].voltage[3];
|
||||
}
|
||||
if (tmp<0) tmp=0.0;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
static float input_freq(void)
|
||||
{
|
||||
if ( !strncmp(types[type].name, "BNT",3) || !strcmp(types[type].name, "KIN"))
|
||||
return 4807.0/raw_data[INPUT_FREQUENCY];
|
||||
else if ( !strcmp(types[type].name, "IMP"))
|
||||
return raw_data[INPUT_FREQUENCY];
|
||||
return raw_data[INPUT_FREQUENCY] ?
|
||||
1.0 / (types[type].freq[0] *
|
||||
raw_data[INPUT_FREQUENCY] +
|
||||
types[type].freq[1]) : 0;
|
||||
}
|
||||
|
||||
static float output_freq(void)
|
||||
{
|
||||
if ( !strncmp(types[type].name, "BNT",3) || !strcmp(types[type].name, "KIN"))
|
||||
return 4807.0/raw_data[OUTPUT_FREQUENCY];
|
||||
else if ( !strcmp(types[type].name, "IMP"))
|
||||
return raw_data[OUTPUT_FREQUENCY];
|
||||
return raw_data[OUTPUT_FREQUENCY] ?
|
||||
1.0 / (types[type].freq[0] *
|
||||
raw_data[OUTPUT_FREQUENCY] +
|
||||
types[type].freq[1]) : 0;
|
||||
}
|
||||
|
||||
static float load_level(void)
|
||||
{
|
||||
unsigned int statINV,model,voltage;
|
||||
int load425[]={99,88,84,80,84,84,84,86,86,81,76};
|
||||
int load525[]={127,113,106,100,106,106,106,109,109,103,97};
|
||||
int load625[]={131,115,107,103,107,107,107,110,110,105,99};
|
||||
int load2k[] ={94,94,94,94,94,94,94,120,120,115,110};
|
||||
int load425i[]={60,54,51,48,51,51,51,53,53,50,48};
|
||||
int load525i[]={81,72,67,62,67,67,67,65,65,62,59};
|
||||
int load625i[]={79,70,67,64,67,67,67,65,65,61,58};
|
||||
int load2ki[] ={84,77,74,70,74,74,74,77,77,74,70};
|
||||
int load400[]={1,1,1,1,1,1,1,1,88,83,87};
|
||||
int load500[]={1,1,1,1,1,1,1,1,108,103,98};
|
||||
int load600[]={1,1,1,1,1,1,1,1,128,123,118};
|
||||
int load400i[]={1,1,1,1,1,1,1,1,54,52,49};
|
||||
int load500i[]={1,1,1,1,1,1,1,1,66,64,61};
|
||||
int load600i[]={1,1,1,1,1,1,1,1,86,84,81};
|
||||
int load801i[]={1,1,1,1,1,1,1,1,44,42,40};
|
||||
int load1000i[]={1,1,1,1,1,1,1,1,56,54,52};
|
||||
int load1200i[]={1,1,1,1,1,1,1,1,76,74,72};
|
||||
|
||||
if ( !strcmp(types[type].name, "BNT") && raw_data[MODELNUMBER]%16 > 7 ) {
|
||||
statINV=raw_data[STATUS_A] & ONLINE;
|
||||
voltage=raw_data[MODELNUMBER]%16;
|
||||
model=BNTmodels[raw_data[MODELNUMBER]/16];
|
||||
if (statINV==0){
|
||||
if (model==400 || model==801)
|
||||
return raw_data[UPS_LOAD]*110.0/load400[voltage];
|
||||
else if (model==600 || model==1200)
|
||||
return raw_data[UPS_LOAD]*110.0/load600[voltage];
|
||||
else
|
||||
return raw_data[UPS_LOAD]*110.0/load500[voltage];
|
||||
} else {
|
||||
switch (model) {
|
||||
case 400: return raw_data[UPS_LOAD]*110.0/load400i[voltage];
|
||||
case 500:
|
||||
case 800: return raw_data[UPS_LOAD]*110.0/load500i[voltage];
|
||||
case 600: return raw_data[UPS_LOAD]*110.0/load600i[voltage];
|
||||
case 801: return raw_data[UPS_LOAD]*110.0/load801i[voltage];
|
||||
case 1200: return raw_data[UPS_LOAD]*110.0/load1200i[voltage];
|
||||
case 1000:
|
||||
case 1500:
|
||||
case 2000: return raw_data[UPS_LOAD]*110.0/load1000i[voltage];
|
||||
}
|
||||
}
|
||||
} else if (!strcmp(types[type].name, "KIN")) {
|
||||
statINV=raw_data[STATUS_A] & ONLINE;
|
||||
voltage=raw_data[MODELNUMBER]%16;
|
||||
model=KINmodels[raw_data[MODELNUMBER]/16];
|
||||
if (statINV==0){
|
||||
if (model==425) return raw_data[UPS_LOAD]*110.0/load425[voltage];
|
||||
if (model==525) return raw_data[UPS_LOAD]*110.0/load525[voltage];
|
||||
if (model==625) return raw_data[UPS_LOAD]*110.0/load625[voltage];
|
||||
if (model<2000) return raw_data[UPS_LOAD]*1.13;
|
||||
if (model>=2000) return raw_data[UPS_LOAD]*110.0/load2k[voltage];
|
||||
} else {
|
||||
if (model==425) return raw_data[UPS_LOAD]*110.0/load425i[voltage];
|
||||
if (model==525) return raw_data[UPS_LOAD]*110.0/load525i[voltage];
|
||||
if (model==625) return raw_data[UPS_LOAD]*110.0/load625i[voltage];
|
||||
if (model<2000) return raw_data[UPS_LOAD]*1.66;
|
||||
if (model>=2000) return raw_data[UPS_LOAD]*110.0/load2ki[voltage];
|
||||
}
|
||||
} else if ( !strcmp(types[type].name, "IMP")) {
|
||||
return raw_data[UPS_LOAD];
|
||||
}
|
||||
return raw_data[STATUS_A] & MAINS_FAILURE ?
|
||||
types[type].loadpct[0] * raw_data[UPS_LOAD] +
|
||||
types[type].loadpct[1] :
|
||||
types[type].loadpct[2] * raw_data[UPS_LOAD] +
|
||||
types[type].loadpct[3];
|
||||
}
|
||||
|
||||
static float batt_level(void)
|
||||
{
|
||||
int bat0,bat29,bat100,model;
|
||||
float battval;
|
||||
|
||||
if ( !strncmp(types[type].name, "BNT",3) ) {
|
||||
bat0=157;
|
||||
bat29=165;
|
||||
bat100=193;
|
||||
battval=(raw_data[UPS_LOAD])/4+raw_data[BATTERY_CHARGE];
|
||||
if (battval<=bat0)
|
||||
return 0.0;
|
||||
if (battval>bat0 && battval<=bat29)
|
||||
return (battval-bat0)*30.0/(bat29-bat0);
|
||||
if (battval>bat29 && battval<=bat100)
|
||||
return 30.0+(battval-bat29)*70.0/(bat100-bat29);
|
||||
return 100.0;
|
||||
}
|
||||
if ( !strcmp(types[type].name, "KIN")) {
|
||||
model=KINmodels[raw_data[MODELNUMBER]/16];
|
||||
if (model>=800 && model<=2000){
|
||||
battval=(raw_data[BATTERY_CHARGE]-165.0)*2.6;
|
||||
if (raw_data[STATUS_A] & ONLINE)
|
||||
return battval+raw_data[UPS_LOAD];
|
||||
if (battval>7)
|
||||
return battval-6;
|
||||
return battval;
|
||||
} else if (model<=625){
|
||||
battval=raw_data[UPS_LOAD]/4.0+raw_data[BATTERY_CHARGE];
|
||||
bat0=169;
|
||||
bat29=176;
|
||||
bat100=204;
|
||||
} else {
|
||||
battval=raw_data[UPS_LOAD]/4.0-raw_data[UPS_LOAD]/32.0+raw_data[BATTERY_CHARGE];
|
||||
bat0=175;
|
||||
bat29=182;
|
||||
bat100=209;
|
||||
}
|
||||
if (battval<=bat0)
|
||||
return 0;
|
||||
if (battval>bat0 && battval<=bat29)
|
||||
return (battval-bat0)*30.0/(bat29-bat0);
|
||||
if (battval>bat29 && battval<=bat100)
|
||||
return 30.0+(battval-bat29)*70.0/(bat100-bat29);
|
||||
return 100;
|
||||
}
|
||||
if ( !strcmp(types[type].name, "IMP"))
|
||||
return raw_data[BATTERY_CHARGE];
|
||||
return raw_data[STATUS_A] & ONLINE ?
|
||||
types[type].battpct[0] * raw_data[BATTERY_CHARGE] +
|
||||
types[type].battpct[1] * load_level() + types[type].battpct[2] :
|
||||
types[type].battpct[3] * raw_data[BATTERY_CHARGE] +
|
||||
types[type].battpct[4];
|
||||
}
|
||||
|
||||
/*
|
||||
* global used functions
|
||||
*/
|
||||
|
||||
/* update information */
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
char val[32];
|
||||
|
||||
if (!ups_getinfo()){
|
||||
return;
|
||||
}
|
||||
|
||||
/* input.frequency */
|
||||
upsdebugx(3, "input.frequency (raw data): [raw: %u]",
|
||||
raw_data[INPUT_FREQUENCY]);
|
||||
dstate_setinfo("input.frequency", "%02.2f", input_freq());
|
||||
upsdebugx(2, "input.frequency: %s", dstate_getinfo("input.frequency"));
|
||||
|
||||
/* output.frequency */
|
||||
upsdebugx(3, "output.frequency (raw data): [raw: %u]",
|
||||
raw_data[OUTPUT_FREQUENCY]);
|
||||
dstate_setinfo("output.frequency", "%02.2f", output_freq());
|
||||
upsdebugx(2, "output.frequency: %s", dstate_getinfo("output.frequency"));
|
||||
|
||||
/* ups.load */
|
||||
upsdebugx(3, "ups.load (raw data): [raw: %u]",
|
||||
raw_data[UPS_LOAD]);
|
||||
dstate_setinfo("ups.load", "%03.1f", load_level());
|
||||
upsdebugx(2, "ups.load: %s", dstate_getinfo("ups.load"));
|
||||
|
||||
/* battery.charge */
|
||||
upsdebugx(3, "battery.charge (raw data): [raw: %u]",
|
||||
raw_data[BATTERY_CHARGE]);
|
||||
dstate_setinfo("battery.charge", "%03.1f", batt_level());
|
||||
upsdebugx(2, "battery.charge: %s", dstate_getinfo("battery.charge"));
|
||||
|
||||
/* input.voltage */
|
||||
upsdebugx(3, "input.voltage (raw data): [raw: %u]",
|
||||
raw_data[INPUT_VOLTAGE]);
|
||||
dstate_setinfo("input.voltage", "%03.1f",input_voltage());
|
||||
upsdebugx(2, "input.voltage: %s", dstate_getinfo("input.voltage"));
|
||||
|
||||
/* output.voltage */
|
||||
upsdebugx(3, "output.voltage (raw data): [raw: %u]",
|
||||
raw_data[OUTPUT_VOLTAGE]);
|
||||
dstate_setinfo("output.voltage", "%03.1f",output_voltage());
|
||||
upsdebugx(2, "output.voltage: %s", dstate_getinfo("output.voltage"));
|
||||
|
||||
status_init();
|
||||
|
||||
*val = 0;
|
||||
if (!(raw_data[STATUS_A] & MAINS_FAILURE)) {
|
||||
!(raw_data[STATUS_A] & OFF) ?
|
||||
status_set("OL") : status_set("OFF");
|
||||
} else {
|
||||
status_set("OB");
|
||||
}
|
||||
|
||||
if (raw_data[STATUS_A] & LOW_BAT) status_set("LB");
|
||||
|
||||
if (raw_data[STATUS_A] & AVR_ON) {
|
||||
input_voltage() < linevoltage ?
|
||||
status_set("BOOST") : status_set("TRIM");
|
||||
}
|
||||
|
||||
if (raw_data[STATUS_A] & OVERLOAD) status_set("OVER");
|
||||
|
||||
if (raw_data[STATUS_B] & BAD_BAT) status_set("RB");
|
||||
|
||||
if (raw_data[STATUS_B] & TEST) status_set("TEST");
|
||||
|
||||
status_commit();
|
||||
|
||||
upsdebugx(2, "STATUS: %s", dstate_getinfo("ups.status"));
|
||||
dstate_dataok();
|
||||
}
|
||||
|
||||
/* shutdown UPS */
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
/* power down the attached load immediately */
|
||||
printf("Forced UPS shutdown (and wait for power)...\n");
|
||||
shutdown_ret();
|
||||
}
|
||||
|
||||
/* initialize UPS */
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
int tmp,model = 0;
|
||||
unsigned int i;
|
||||
static char buf[20];
|
||||
|
||||
/* check manufacturer name from arguments */
|
||||
if (getval("manufacturer") != NULL)
|
||||
manufacturer = getval("manufacturer");
|
||||
|
||||
/* check model name from arguments */
|
||||
if (getval("modelname") != NULL)
|
||||
modelname = getval("modelname");
|
||||
|
||||
/* check serial number from arguments */
|
||||
if (getval("serialnumber") != NULL)
|
||||
serialnumber = getval("serialnumber");
|
||||
|
||||
/* get and check type */
|
||||
if (getval("type") != NULL) {
|
||||
for (i = 0;
|
||||
i < NUM_OF_SUBTYPES && strcmp(types[i].name, getval("type"));
|
||||
i++) ;
|
||||
if (i >= NUM_OF_SUBTYPES) {
|
||||
printf("Given UPS type '%s' isn't valid!\n", getval("type"));
|
||||
exit (1);
|
||||
}
|
||||
type = i;
|
||||
};
|
||||
|
||||
/* check line voltage from arguments */
|
||||
if (getval("linevoltage") != NULL) {
|
||||
tmp = atoi(getval("linevoltage"));
|
||||
if (! ( (tmp >= 200 && tmp <= 240) || (tmp >= 100 && tmp <= 120) ) ) {
|
||||
printf("Given line voltage '%d' is out of range (100-120 or 200-240 V)\n", tmp);
|
||||
exit (1);
|
||||
};
|
||||
linevoltage = (unsigned int) tmp;
|
||||
};
|
||||
|
||||
if (getval("numOfBytesFromUPS") != NULL) {
|
||||
tmp = atoi(getval("numOfBytesFromUPS"));
|
||||
if (! (tmp > 0 && tmp <= MAX_NUM_OF_BYTES_FROM_UPS) ) {
|
||||
printf("Given numOfBytesFromUPS '%d' is out of range (1 to %d)\n",
|
||||
tmp, MAX_NUM_OF_BYTES_FROM_UPS);
|
||||
exit (1);
|
||||
};
|
||||
types[type].num_of_bytes_from_ups = (unsigned char) tmp;
|
||||
}
|
||||
|
||||
if (getval("methodOfFlowControl") != NULL) {
|
||||
for (i = 0;
|
||||
i < NUM_OF_SUBTYPES &&
|
||||
strcmp(types[i].flowControl.name,
|
||||
getval("methodOfFlowControl"));
|
||||
i++) ;
|
||||
if (i >= NUM_OF_SUBTYPES) {
|
||||
printf("Given methodOfFlowControl '%s' isn't valid!\n",
|
||||
getval("methodOfFlowControl"));
|
||||
exit (1);
|
||||
};
|
||||
types[type].flowControl = types[i].flowControl;
|
||||
}
|
||||
|
||||
if (getval("validationSequence") &&
|
||||
sscanf(getval("validationSequence"),
|
||||
"{{%u,%x},{%u,%x},{%u,%x}}",
|
||||
&types[type].validation[0].index_of_byte,
|
||||
&types[type].validation[0].required_value,
|
||||
&types[type].validation[1].index_of_byte,
|
||||
&types[type].validation[1].required_value,
|
||||
&types[type].validation[2].index_of_byte,
|
||||
&types[type].validation[2].required_value
|
||||
) < 6
|
||||
) {
|
||||
printf("Given validationSequence '%s' isn't valid!\n",
|
||||
getval("validationSequence"));
|
||||
exit (1);
|
||||
}
|
||||
|
||||
if (getval("shutdownArguments") &&
|
||||
sscanf(getval("shutdownArguments"), "{{%u,%u},%c}",
|
||||
&types[type].shutdown_arguments.delay[0],
|
||||
&types[type].shutdown_arguments.delay[1],
|
||||
&types[type].shutdown_arguments.minutesShouldBeUsed
|
||||
) < 3
|
||||
) {
|
||||
printf("Given shutdownArguments '%s' isn't valid!\n",
|
||||
getval("shutdownArguments"));
|
||||
exit (1);
|
||||
}
|
||||
|
||||
if (getval("frequency") &&
|
||||
sscanf(getval("frequency"), "{%f,%f}",
|
||||
&types[type].freq[0], &types[type].freq[1]
|
||||
) < 2
|
||||
) {
|
||||
printf("Given frequency '%s' isn't valid!\n",
|
||||
getval("frequency"));
|
||||
exit (1);
|
||||
}
|
||||
|
||||
if (getval("loadPercentage") &&
|
||||
sscanf(getval("loadPercentage"), "{%f,%f,%f,%f}",
|
||||
&types[type].loadpct[0], &types[type].loadpct[1],
|
||||
&types[type].loadpct[2], &types[type].loadpct[3]
|
||||
) < 4
|
||||
) {
|
||||
printf("Given loadPercentage '%s' isn't valid!\n",
|
||||
getval("loadPercentage"));
|
||||
exit (1);
|
||||
}
|
||||
|
||||
if (getval("batteryPercentage") &&
|
||||
sscanf(getval("batteryPercentage"), "{%f,%f,%f,%f,%f}",
|
||||
&types[type].battpct[0], &types[type].battpct[1],
|
||||
&types[type].battpct[2], &types[type].battpct[3],
|
||||
&types[type].battpct[4]
|
||||
) < 5
|
||||
) {
|
||||
printf("Given batteryPercentage '%s' isn't valid!\n",
|
||||
getval("batteryPercentage"));
|
||||
exit (1);
|
||||
}
|
||||
|
||||
if (getval("voltage") &&
|
||||
sscanf(getval("voltage"), "{%f,%f,%f,%f}",
|
||||
&types[type].voltage[0], &types[type].voltage[1],
|
||||
&types[type].voltage[2], &types[type].voltage[3]
|
||||
) < 4
|
||||
) {
|
||||
printf("Given voltage '%s' isn't valid!\n", getval("voltage"));
|
||||
exit (1);
|
||||
}
|
||||
|
||||
/* open serial port */
|
||||
upsfd = ser_open(device_path);
|
||||
ser_set_speed(upsfd, device_path, B1200);
|
||||
|
||||
/* setup flow control */
|
||||
types[type].flowControl.setup_flow_control();
|
||||
if (!strncmp(types[type].name, "BNT",3) || !strcmp(types[type].name, "KIN") || !strcmp(types[type].name, "IMP")){
|
||||
if (!ups_getinfo()) return;
|
||||
if (raw_data[UPSVERSION]==0xFF){
|
||||
types[type].name="IMP";
|
||||
model=IMPmodels[raw_data[MODELNUMBER]/16];
|
||||
}
|
||||
if (raw_data[MODELNAME]==0x42){
|
||||
if (!strcmp(types[type].name, "BNT-other"))
|
||||
types[type].name="BNT-other";
|
||||
else
|
||||
types[type].name="BNT";
|
||||
model=BNTmodels[raw_data[MODELNUMBER]/16];
|
||||
}
|
||||
if (raw_data[MODELNAME]==0x4B){
|
||||
types[type].name="KIN";
|
||||
model=KINmodels[raw_data[MODELNUMBER]/16];
|
||||
}
|
||||
linevoltage=voltages[raw_data[MODELNUMBER]%16];
|
||||
snprintf(buf,sizeof(buf),"%s-%dAP",types[type].name,model);
|
||||
modelname=buf;
|
||||
upsdebugx(1,"Detected: %s , %dV",modelname,linevoltage);
|
||||
if (ser_send_char (upsfd, BATTERY_TEST) != 1) {
|
||||
upslogx(LOG_NOTICE, "writing error");
|
||||
dstate_datastale();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
upsdebugx(1, "Values of arguments:");
|
||||
upsdebugx(1, " manufacturer : '%s'", manufacturer);
|
||||
upsdebugx(1, " model name : '%s'", modelname);
|
||||
upsdebugx(1, " serial number : '%s'", serialnumber);
|
||||
upsdebugx(1, " line voltage : '%u'", linevoltage);
|
||||
upsdebugx(1, " type : '%s'", types[type].name);
|
||||
upsdebugx(1, " number of bytes from UPS: '%u'",
|
||||
types[type].num_of_bytes_from_ups);
|
||||
upsdebugx(1, " method of flow control : '%s'",
|
||||
types[type].flowControl.name);
|
||||
upsdebugx(1, " validation sequence: '{{%u,%#x},{%u,%#x},{%u,%#x}}'",
|
||||
types[type].validation[0].index_of_byte,
|
||||
types[type].validation[0].required_value,
|
||||
types[type].validation[1].index_of_byte,
|
||||
types[type].validation[1].required_value,
|
||||
types[type].validation[2].index_of_byte,
|
||||
types[type].validation[2].required_value);
|
||||
upsdebugx(1, " shutdown arguments: '{{%u,%u},%c}'",
|
||||
types[type].shutdown_arguments.delay[0],
|
||||
types[type].shutdown_arguments.delay[1],
|
||||
types[type].shutdown_arguments.minutesShouldBeUsed);
|
||||
if ( strcmp(types[type].name, "KIN") && strcmp(types[type].name, "BNT") && strcmp(types[type].name, "IMP")) {
|
||||
upsdebugx(1, " frequency calculation coefficients: '{%f,%f}'",
|
||||
types[type].freq[0], types[type].freq[1]);
|
||||
upsdebugx(1, " load percentage calculation coefficients: "
|
||||
"'{%f,%f,%f,%f}'",
|
||||
types[type].loadpct[0], types[type].loadpct[1],
|
||||
types[type].loadpct[2], types[type].loadpct[3]);
|
||||
upsdebugx(1, " battery percentage calculation coefficients: "
|
||||
"'{%f,%f,%f,%f,%f}'",
|
||||
types[type].battpct[0], types[type].battpct[1],
|
||||
types[type].battpct[2], types[type].battpct[3],
|
||||
types[type].battpct[4]);
|
||||
upsdebugx(1, " voltage calculation coefficients: '{%f,%f}'",
|
||||
types[type].voltage[2], types[type].voltage[3]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* display help */
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
printf("You must specify type in ups.conf\n");
|
||||
printf("Type of UPS like 'Trust', 'Egys', 'KP625AP', 'IMP', 'KIN' or 'BNT' or 'BNT-other' (default: 'Trust')\n");
|
||||
printf("BNT-other - it's a special type for BNT 100-120V models \n");
|
||||
printf("You can additional cpecify next variables:\n");
|
||||
printf(" shutdownArguments: The number of delay arguments and their values for the shutdown operation\n");
|
||||
printf("Also, you can specify next variables (not work for 'IMP', 'KIN' or 'BNT', because detected automatically or known\n");
|
||||
printf(" manufacturer: Specify manufacturer name (default: 'PowerCom')\n");
|
||||
printf(" modelname: Specify model name, because it cannot detect automagically (default: Unknown)\n");
|
||||
printf(" serialnumber: Specify serial number, because it cannot detect automatically (default: Unknown)\n");
|
||||
printf(" linevoltage: Specify line voltage (110-120 or 220-240 V), if it cannot detect automatically (default: 230 V)\n");
|
||||
printf(" numOfBytesFromUPS: The number of bytes in a UPS frame\n");
|
||||
printf(" methodOfFlowControl: The flow control method engaged by the UPS\n");
|
||||
printf(" validationSequence: 3 pairs to be used for validating the UPS\n");
|
||||
printf(" voltage: A quad to convert the raw data to human readable voltage\n");
|
||||
printf(" frequency: A pair to convert the raw data to human readable freqency\n");
|
||||
printf(" batteryPercentage: A 5 tuple to convert the raw data to human readable battery percentage\n");
|
||||
printf(" loadPercentage: A quad to convert the raw data to human readable load percentage\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* initialize information */
|
||||
void upsdrv_initinfo(void)
|
||||
{
|
||||
/* write constant data for this model */
|
||||
dstate_setinfo ("ups.mfr", "%s", manufacturer);
|
||||
dstate_setinfo ("ups.model", "%s", modelname);
|
||||
dstate_setinfo ("ups.serial", "%s", serialnumber);
|
||||
dstate_setinfo ("ups.model.type", "%s", types[type].name);
|
||||
dstate_setinfo ("input.voltage.nominal", "%u", linevoltage);
|
||||
|
||||
/* now add the instant commands */
|
||||
dstate_addcmd ("test.battery.start");
|
||||
dstate_addcmd ("shutdown.return");
|
||||
dstate_addcmd ("shutdown.stayoff");
|
||||
upsh.instcmd = instcmd;
|
||||
}
|
||||
|
||||
/* define possible arguments */
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
addvar(VAR_VALUE, "manufacturer", "Specify manufacturer name (default: 'PowerCom')");
|
||||
addvar(VAR_VALUE, "linevoltage", "Specify line voltage (110-120 or 220-240 V), if it cannot detect automatically (default: 230 V)");
|
||||
addvar(VAR_VALUE, "modelname", "Specify model name, because it cannot detect automagically (default: Unknown)");
|
||||
addvar(VAR_VALUE, "serialnumber", "Specify serial number, because it cannot detect automatically (default: Unknown)");
|
||||
addvar(VAR_VALUE, "type", "Type of UPS like 'Trust', 'Egys', 'KP625AP', 'IMP', 'KIN' or 'BNT' or 'BNT-other' (default: 'Trust')");
|
||||
addvar(VAR_VALUE, "numOfBytesFromUPS", "The number of bytes in a UPS frame");
|
||||
addvar(VAR_VALUE, "methodOfFlowControl", "The flow control method engaged by the UPS");
|
||||
addvar(VAR_VALUE, "shutdownArguments", "The number of delay arguments and their values for the shutdown operation");
|
||||
addvar(VAR_VALUE, "validationSequence", "3 pairs to be used for validating the UPS");
|
||||
if ( strcmp(types[type].name, "KIN") && strcmp(types[type].name, "BNT") && strcmp(types[type].name, "IMP")) {
|
||||
addvar(VAR_VALUE, "voltage", "A quad to convert the raw data to human readable voltage");
|
||||
addvar(VAR_VALUE, "frequency", "A pair to convert the raw data to human readable freqency");
|
||||
addvar(VAR_VALUE, "batteryPercentage", "A 5 tuple to convert the raw data to human readable battery percentage");
|
||||
addvar(VAR_VALUE, "loadPercentage", "A quad to convert the raw data to human readable load percentage");
|
||||
}
|
||||
}
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
ser_close(upsfd, device_path);
|
||||
}
|
||||
101
drivers/powercom.h
Normal file
101
drivers/powercom.h
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* powercom.h - defines for the newpowercom.c driver
|
||||
*
|
||||
* $Id: powercom.h 2336 2010-02-11 20:16:43Z adkorte-guest $
|
||||
*
|
||||
* Copyrights:
|
||||
* (C) 2002 Simon Rozman <simon@rozman.net>
|
||||
* (C) 1999 Peter Bieringer <pb@bieringer.de>
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
/* C-libary includes */
|
||||
#include <sys/file.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/types.h>
|
||||
#include "serial.h"
|
||||
#include <limits.h>
|
||||
|
||||
/* nut includes */
|
||||
#include "timehead.h"
|
||||
|
||||
|
||||
/* supported types */
|
||||
struct type {
|
||||
const char *name;
|
||||
unsigned char num_of_bytes_from_ups;
|
||||
|
||||
struct method_of_flow_control {
|
||||
char *name;
|
||||
void (*setup_flow_control)(void);
|
||||
} flowControl;
|
||||
|
||||
struct validation_byte {
|
||||
unsigned int index_of_byte, required_value;
|
||||
/* An example might explain the intention better then prose.
|
||||
* Suppose we want to validate the data with:
|
||||
* powercom_raw_data[5] == 0x80
|
||||
* then we will set index_of_byte to 5U and required_value to
|
||||
* 0x80U: { 5U, 0x80U }.
|
||||
*/
|
||||
} validation[3];
|
||||
/* The validation array is of length 3 because 3 is longest
|
||||
* validation sequence for any type.
|
||||
*/
|
||||
|
||||
/* Some UPSs must have a minutes and a seconds arguments for
|
||||
* the COUNTER commands while others are known to work with the
|
||||
* seconds argument alone.
|
||||
*/
|
||||
struct deley_for_power_kill {
|
||||
unsigned int delay[2]; /* { minutes, seconds } */
|
||||
unsigned char minutesShouldBeUsed;
|
||||
/* 'n' in case the minutes value, which is deley[0], should
|
||||
* be skipped and not sent to the UPS.
|
||||
*/
|
||||
} shutdown_arguments;
|
||||
|
||||
/* parameters to calculate input and output freq., one pair for
|
||||
* each type:
|
||||
* Each pair defines parameters for 1/(A*x+B) to calculate freq.
|
||||
* from raw data
|
||||
*/
|
||||
float freq[2];
|
||||
|
||||
/* parameters to calculate load %, two pairs for each type:
|
||||
* First pair defines the parameters for A*x+B to calculate load
|
||||
* from raw data when offline and the second pair is used when
|
||||
* online
|
||||
*/
|
||||
float loadpct[4];
|
||||
|
||||
/* parameters to calculate battery %, five parameters for each type:
|
||||
* First three params defines the parameters for A*x+B*y+C to calculate
|
||||
* battery % (x is raw data, y is load %) when offline.
|
||||
* Fourth and fifth parameters are used to calculate D*y+E when online.
|
||||
*/
|
||||
float battpct[5];
|
||||
|
||||
/* parameters to calculate utility and output voltage, two pairs for
|
||||
* each type:
|
||||
* First pair defines the parameters for A*x+B to calculate utility
|
||||
* from raw data when line voltage is >=220 and the second pair
|
||||
* is used otherwise.
|
||||
*/
|
||||
float voltage[4];
|
||||
};
|
||||
279
drivers/powerman-pdu.c
Normal file
279
drivers/powerman-pdu.c
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
/* powerman-pdu.c - Powerman PDU client driver
|
||||
*
|
||||
* Copyright (C) 2008 Arnaud Quette <aquette.dev@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
|
||||
#include <libpowerman.h>
|
||||
|
||||
#define DRIVER_NAME "Powerman PDU client driver"
|
||||
#define DRIVER_VERSION "0.11"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Arnaud Quette <aquette.dev@gmail.com>",
|
||||
DRV_EXPERIMENTAL,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
/* Powerman functions and variables */
|
||||
static pm_err_t query_one(pm_handle_t pm, char *s, int mode);
|
||||
static pm_err_t query_all(pm_handle_t pm, int mode);
|
||||
|
||||
pm_handle_t pm;
|
||||
char ebuf[64];
|
||||
|
||||
/* modes to snmp_ups_walk. */
|
||||
#define WALKMODE_INIT 0
|
||||
#define WALKMODE_UPDATE 1
|
||||
|
||||
static int reconnect_ups(void);
|
||||
|
||||
static int instcmd(const char *cmdname, const char *extra)
|
||||
{
|
||||
pm_err_t rv = -1;
|
||||
char *cmdsuffix = NULL;
|
||||
char *cmdindex = NULL;
|
||||
char *outletname = NULL;
|
||||
|
||||
upsdebugx(1, "entering instcmd (%s)", cmdname);
|
||||
|
||||
/* only consider the end of the command */
|
||||
if ( (cmdsuffix = strrchr(cmdname, '.')) == NULL )
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
else
|
||||
cmdsuffix++;
|
||||
|
||||
/* get the outlet name */
|
||||
if ( (cmdindex = strchr(cmdname, '.')) == NULL )
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
else {
|
||||
char buf[32];
|
||||
cmdindex++;
|
||||
snprintf(buf, sizeof(buf), "outlet.%i.desc", atoi(cmdindex));
|
||||
outletname = (char *)dstate_getinfo(buf);
|
||||
}
|
||||
|
||||
/* Power on the outlet */
|
||||
if (!strcasecmp(cmdsuffix, "on")) {
|
||||
rv = pm_node_on(pm, outletname);
|
||||
return (rv==PM_ESUCCESS)?STAT_INSTCMD_HANDLED:STAT_SET_INVALID;
|
||||
}
|
||||
|
||||
/* Power off the outlet */
|
||||
if (!strcasecmp(cmdsuffix, "off")) {
|
||||
rv = pm_node_off(pm, outletname);
|
||||
return (rv==PM_ESUCCESS)?STAT_INSTCMD_HANDLED:STAT_SET_INVALID;
|
||||
}
|
||||
|
||||
/* Cycle the outlet */
|
||||
if (!strcasecmp(cmdsuffix, "cycle")) {
|
||||
rv = pm_node_cycle(pm, outletname);
|
||||
return (rv==PM_ESUCCESS)?STAT_INSTCMD_HANDLED:STAT_SET_INVALID;
|
||||
}
|
||||
|
||||
upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
|
||||
return STAT_INSTCMD_UNKNOWN;
|
||||
}
|
||||
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
pm_err_t rv = PM_ESUCCESS;
|
||||
|
||||
if ( (rv = query_all(pm, WALKMODE_UPDATE)) != PM_ESUCCESS) {
|
||||
upslogx(2, "Error: %s (%i)\n", pm_strerror(rv, ebuf, sizeof(ebuf)), errno);
|
||||
/* FIXME: try to reconnect?
|
||||
* dstate_datastale();
|
||||
*/
|
||||
reconnect_ups();
|
||||
}
|
||||
}
|
||||
|
||||
void upsdrv_initinfo(void)
|
||||
{
|
||||
pm_err_t rv = PM_ESUCCESS;
|
||||
|
||||
/* try to detect the PDU here - call fatal_with_errno(EXIT_FAILURE, ) if it fails */
|
||||
|
||||
/* FIXME: can we report something useful? */
|
||||
dstate_setinfo("ups.mfr", "Powerman");
|
||||
dstate_setinfo("device.mfr", "Powerman");
|
||||
dstate_setinfo("ups.model", "unknown PDU");
|
||||
dstate_setinfo("device.model", "unknown PDU");
|
||||
dstate_setinfo("device.type", "pdu");
|
||||
|
||||
/* Now walk the data tree */
|
||||
if ( (rv = query_all(pm, WALKMODE_INIT)) != PM_ESUCCESS) {
|
||||
upslogx(2, "Error: %s\n", pm_strerror(rv, ebuf, sizeof(ebuf)));
|
||||
/* FIXME: try to reconnect?
|
||||
* dstate_datastale();
|
||||
*/
|
||||
reconnect_ups();
|
||||
|
||||
}
|
||||
upsh.instcmd = instcmd;
|
||||
/* FIXME: no need for setvar (ex for outlet.n.delay.*)!? */
|
||||
}
|
||||
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
/* FIXME: shutdown all outlets? */
|
||||
fatalx(EXIT_FAILURE, "shutdown not supported");
|
||||
|
||||
/* OL: this must power cycle the load if possible */
|
||||
/* OB: the load must remain off until the power returns */
|
||||
}
|
||||
|
||||
/*
|
||||
static int setvar(const char *varname, const char *val)
|
||||
{
|
||||
if (!strcasecmp(varname, "outlet.n.delay.*")) {
|
||||
...
|
||||
return STAT_SET_HANDLED;
|
||||
}
|
||||
|
||||
upslogx(LOG_NOTICE, "setvar: unknown variable [%s]", varname);
|
||||
return STAT_SET_UNKNOWN;
|
||||
}
|
||||
*/
|
||||
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
}
|
||||
|
||||
/* list flags and values that you want to receive via -x */
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
/* FIXME: anything useful to be put here? */
|
||||
}
|
||||
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
pm_err_t rv = PM_ESUCCESS;
|
||||
|
||||
/* Connect to the PowerMan daemon */
|
||||
if ((rv = pm_connect(device_path, NULL, &pm, 0)) != PM_ESUCCESS) {
|
||||
fatalx(EXIT_FAILURE, "Can't connect to %s: %s\n", device_path,
|
||||
pm_strerror(rv, ebuf, sizeof(ebuf)));
|
||||
}
|
||||
|
||||
/* FIXME: suitable?
|
||||
* poll_interval = 30; */
|
||||
}
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
pm_disconnect(pm);
|
||||
}
|
||||
|
||||
static int reconnect_ups(void)
|
||||
{
|
||||
pm_err_t rv;
|
||||
|
||||
upsdebugx(4, "==================================================");
|
||||
upsdebugx(4, "= connexion lost with Powerman, try to reconnect =");
|
||||
upsdebugx(4, "==================================================");
|
||||
|
||||
/* clear the situation */
|
||||
pm_disconnect(pm);
|
||||
|
||||
/* Connect to the PowerMan daemon */
|
||||
if ((rv = pm_connect(device_path, NULL, &pm, 0)) != PM_ESUCCESS)
|
||||
return 0;
|
||||
else {
|
||||
upsdebugx(4, "connexion restored with Powerman");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* powerman support functions
|
||||
****************************/
|
||||
|
||||
static pm_err_t query_one(pm_handle_t pm, char *s, int outletnum)
|
||||
{
|
||||
pm_err_t rv;
|
||||
pm_node_state_t ns;
|
||||
char outlet_prop[64];
|
||||
|
||||
upsdebugx(1, "entering query_one (%s)", s);
|
||||
|
||||
rv = pm_node_status(pm, s, &ns);
|
||||
if (rv == PM_ESUCCESS) {
|
||||
|
||||
upsdebugx(3, "updating status");
|
||||
|
||||
snprintf(outlet_prop, sizeof(outlet_prop), "outlet.%i.status", outletnum);
|
||||
dstate_setinfo(outlet_prop, "%s", ns == PM_ON ? "on" :
|
||||
ns == PM_OFF ? "off" : "unknown");
|
||||
dstate_dataok();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
static pm_err_t query_all(pm_handle_t pm, int mode)
|
||||
{
|
||||
pm_err_t rv;
|
||||
pm_node_iterator_t itr;
|
||||
char outlet_prop[64];
|
||||
char *s;
|
||||
int outletnum = 1;
|
||||
|
||||
upsdebugx(1, "entering query_all ()");
|
||||
|
||||
rv = pm_node_iterator_create(pm, &itr);
|
||||
if (rv != PM_ESUCCESS)
|
||||
return rv;
|
||||
|
||||
while ((s = pm_node_next(itr))) {
|
||||
|
||||
/* in WALKMODE_UPDATE, we always call this one for the
|
||||
* status update... */
|
||||
if ((rv = query_one(pm, s, outletnum)) != PM_ESUCCESS)
|
||||
break;
|
||||
else {
|
||||
/* set the initial generic properties (ie except status)
|
||||
* but only if the status query succeeded */
|
||||
if (mode == WALKMODE_INIT) {
|
||||
snprintf(outlet_prop, sizeof(outlet_prop), "outlet.%i.id", outletnum);
|
||||
dstate_setinfo(outlet_prop, "%i", outletnum);
|
||||
|
||||
snprintf(outlet_prop, sizeof(outlet_prop), "outlet.%i.desc", outletnum);
|
||||
dstate_setinfo(outlet_prop, "%s", s);
|
||||
|
||||
/* we assume it's always true! */
|
||||
snprintf(outlet_prop, sizeof(outlet_prop), "outlet.%i.switchable", outletnum);
|
||||
dstate_setinfo(outlet_prop, "yes");
|
||||
|
||||
/* add instant commands */
|
||||
snprintf(outlet_prop, sizeof(outlet_prop), "outlet.%i.load.on", outletnum);
|
||||
dstate_addcmd(outlet_prop);
|
||||
snprintf(outlet_prop, sizeof(outlet_prop), "outlet.%i.load.off", outletnum);
|
||||
dstate_addcmd(outlet_prop);
|
||||
snprintf(outlet_prop, sizeof(outlet_prop), "outlet.%i.load.cycle", outletnum);
|
||||
dstate_addcmd(outlet_prop);
|
||||
}
|
||||
}
|
||||
outletnum++;
|
||||
}
|
||||
pm_node_iterator_destroy(itr);
|
||||
return rv;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue