Imported Upstream version 2.4.3

This commit is contained in:
arnaud.quette@free.fr 2010-03-26 00:20:59 +01:00
commit 26fb71b504
446 changed files with 148951 additions and 0 deletions

235
drivers/Makefile.am Normal file
View 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

File diff suppressed because it is too large Load diff

401
drivers/apc-hid.c Normal file
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

285
drivers/apcsmart.h Normal file
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

364
drivers/bcmxcp.h Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

766
drivers/bestfcom.c Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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(&regex_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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

42
drivers/megatec.h Normal file
View 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
View 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(&regex_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

File diff suppressed because it is too large Load diff

1018
drivers/mge-hid.c Normal file

File diff suppressed because it is too large Load diff

32
drivers/mge-hid.h Normal file
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

523
drivers/mge-shut.h Normal file
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

28
drivers/mge-xml.h Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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