/* mge-shut.c - 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>
* Russell Kroll <rkroll@exploits.org>
*
* 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 <sys/ioctl.h>
#include <string.h>
#include "config.h"
#include "main.h"
#include "serial.h"
#include "timehead.h"
#include "mge-shut.h"
#include "hidparser.h"
#include "hidtypes.h"
#include "common.h" /* for upsdebugx() etc */
/* --------------------------------------------------------------- */
/* Define "technical" constants */
/* --------------------------------------------------------------- */
#define DRIVER_NAME "MGE UPS SYSTEMS/SHUT driver"
#define MAX_TRY 4
/* global variables */
int commstatus = 0;
int lowbatt = DEFAULT_LOWBATT;
int ondelay = DEFAULT_ONDELAY;
int offdelay = DEFAULT_OFFDELAY;
int notification = DEFAULT_NOTIFICATION;
#define SD_RETURN 0
#define SD_STAYOFF 1
int sdtype = SD_RETURN;
#define BYTESWAP(in) (((in & 0xFF) << 8) + ((in & 0xFF00) >> 8))
/* realign packet data according to Endianess */
static void align_request(hid_packet_t *sd)
{
#if WORDS_BIGENDIAN
/* Sparc/Mips/... are big endian, USB/SHUT little endian */
(*sd).wValue = BYTESWAP((*sd).wValue);
(*sd).wIndex = BYTESWAP((*sd).wIndex);
(*sd).wLength = BYTESWAP((*sd).wLength);
#endif
}
/* --------------------------------------------------------------- */
/* Global structures */
/* --------------------------------------------------------------- */
hid_desc_data_t hid_descriptor;
device_desc_data_t device_descriptor;
static long hValue;
static HIDDesc_t *pDesc = NULL; /* parsed Report Descriptor */
u_char raw_buf[4096];
/* --------------------------------------------------------------- */
/* Function prototypes */
/* --------------------------------------------------------------- */
float expo(int a, int b);
extern long FormatValue(long Value, u_char Size);
static char *hu_find_infoval(info_lkp_t *hid2info, long value);
/* --------------------------------------------------------------- */
/* UPS Driver Functions */
/* --------------------------------------------------------------- */
void upsdrv_initinfo (void)
{
mge_info_item_t *item;
upsdebugx(2, "entering initinfo()\n");
/* Get complete Model information */
shut_identify_ups ();
printf("Detected %s [%s] on %s\n", dstate_getinfo("ups.model"),
dstate_getinfo("ups.serial"), device_path);
/* Device capabilities enumeration ----------------------------- */
for ( item = mge_info ; item->type != NULL ; item++ ) {
/* Check if we are asked to stop (reactivity++) */
if (exit_flag != 0)
return;
/* avoid redundancy when multiple defines (RO/RW) */
if (dstate_getinfo(item->type) != NULL)
continue;
/* Special case for handling server side variables */
if (item->shut_flags & SHUT_FLAG_ABSENT) {
/* Check if exists (if necessary) before creation */
if (item->item_path != NULL)
{
if (hid_get_value(item->item_path) != 1 )
continue;
}
else
{
/* Simply set the default value */
dstate_setinfo(item->type, "%s", item->dfl);
dstate_setflags(item->type, item->flags);
continue;
}
dstate_setinfo(item->type, "%s", item->dfl);
dstate_setflags(item->type, item->flags);
/* Set max length for strings, if needed */
if (item->flags & ST_FLAG_STRING)
dstate_setaux(item->type, item->length);
/* disable reading now
item->shut_flags &= ~SHUT_FLAG_OK;*/
} else {
if (hid_get_value(item->item_path) != 0 ) {
item->shut_flags &= SHUT_FLAG_OK;
dstate_setinfo(item->type, item->fmt, hValue);
dstate_setflags(item->type, item->flags);
/* Set max length for strings */
if (item->flags & ST_FLAG_STRING)
dstate_setaux(item->type, item->length);
}
else {
item->shut_flags &= ~SHUT_FLAG_OK;
}
}
}
/* commands ----------------------------------------------- */
dstate_addcmd("load.off");
dstate_addcmd("load.on");
dstate_addcmd("shutdown.return");
dstate_addcmd("shutdown.stayoff");
dstate_addcmd("test.battery.start");
dstate_addcmd("test.battery.stop");
/* install handlers */
upsh.setvar = hid_set_value; /* setvar; */
upsh.instcmd = instcmd;
}
/* --------------------------------------------------------------- */
void upsdrv_updateinfo (void)
{
mge_info_item_t *item;
char *nutvalue;
upsdebugx(2, "entering upsdrv_updateinfo()");
if (commstatus == 0) {
if (shut_ups_start () != 0) {
upsdebugx(2, "No communication with UPS, retrying");
dstate_datastale();
sleep (10);
} else {
upsdebugx(2, "Communication with UPS established");
}
}
shut_ups_status();
/* Device data walk ----------------------------- */
for ( item = mge_info ; item->type != NULL; item++ ) {
/* Check if we are asked to stop (reactivity++) */
if (exit_flag != 0)
return;
if (item->shut_flags & SHUT_FLAG_ABSENT)
continue;
if (item->shut_flags & SHUT_FLAG_OK) {
if(hid_get_value(item->item_path) != 0 ) {
upsdebugx(3, "%s: hValue = %ld", item->item_path, hValue);
/* upsdebugx(3, "%s: hValue = %ld (%ld)",
item->item_path, hValue, hData.LogMax); */
/* need lookup'ed translation */
if (item->hid2info != NULL)
{
nutvalue = hu_find_infoval(item->hid2info, (long)hValue);
if (nutvalue != NULL)
dstate_setinfo(item->type, "%s", nutvalue);
else
dstate_setinfo(item->type, item->fmt, hValue);
}
else
dstate_setinfo(item->type, item->fmt, hValue);
dstate_dataok();
} else {
if (shut_ups_start () != 0)
dstate_datastale();
}
}
}
}
/* --------------------------------------------------------------- */
void upsdrv_shutdown (void)
{
char val[5];
if (sdtype == SD_RETURN) {
/* set DelayBeforeStartup */
sprintf(val, "%d", ondelay);
hid_set_value("ups.delay.start", val);
}
/* set DelayBeforeShutdown */
sprintf(val, "%d", offdelay);
hid_set_value("ups.delay.shutdown", val);
}
/* --------------------------------------------------------------- */
void upsdrv_help (void)
{
upsdebugx(2, "entering upsdrv_help");
}
/* --------------------------------------------------------------- */
/* list flags and values that you want to receive via -x */
void upsdrv_makevartable (void)
{
char msg[MAX_STRING];
upsdebugx (2, "entering upsdrv_makevartable()");
sprintf(msg, "Set low battery level, in %% (default=%d).",
DEFAULT_LOWBATT);
addvar (VAR_VALUE, "lowbatt", msg);
sprintf(msg, "Set shutdown delay, in seconds (default=%d).",
DEFAULT_OFFDELAY);
addvar (VAR_VALUE, "offdelay", msg);
sprintf(msg, "Set startup delay, in ten seconds units (default=%d).",
DEFAULT_ONDELAY);
addvar (VAR_VALUE, "ondelay", msg);
sprintf(msg, "Set notification type, 1 = no, 2 = light, 3 = yes (default=%d).",
DEFAULT_NOTIFICATION);
addvar (VAR_VALUE, "notification", msg);
}
/* --------------------------------------------------------------- */
void upsdrv_banner (void)
{
printf("Network UPS Tools - %s %s (%s)\n",
DRIVER_NAME, DRIVER_VERSION, UPS_VERSION);
}
/* --------------------------------------------------------------- */
void upsdrv_initups (void)
{
upsdebugx(2, "entering upsdrv_initups()");
/* initialize serial port */
upsfd = ser_open(device_path);
ser_set_speed(upsfd, device_path, B2400);
setline (1);
/* get battery lowlevel */
if (getval ("lowbatt"))
lowbatt = atoi (getval ("lowbatt"));
/* on delay */
if (getval ("ondelay"))
ondelay = atoi (getval ("ondelay"));
/* shutdown delay */
if (getval ("offdelay"))
offdelay = atoi (getval ("offdelay"));
/* notification type */
if (getval ("notification"))
notification = atoi (getval ("notification"));
/* initialise communication */
if (shut_ups_start () != 0)
fatalx("No communication with UPS");
else
upsdebugx(2, "Communication with UPS established");
/* initialise HID communication */
if(hid_init_device() != 0)
fatalx("Can't initialise HID device");
}
/* --------------------------------------------------------------- */
void upsdrv_cleanup(void)
{
ser_close(upsfd, device_path);
}
/* --------------------------------------------------------------- */
int instcmd(const char *cmdname, const char *extra)
{
/* Shutdown UPS and return when power is restored */
if (!strcasecmp(cmdname, "shutdown.return")) {
sdtype = SD_RETURN;
upsdrv_shutdown();
return STAT_INSTCMD_HANDLED;
}
/* Shutdown UPS and stay off when power is restored */
if (!strcasecmp(cmdname, "shutdown.stayoff")) {
sdtype = SD_STAYOFF;
upsdrv_shutdown();
return STAT_INSTCMD_HANDLED;
}
/* Power off the load immediatly */
if (!strcasecmp(cmdname, "load.off")) {
/* set DelayBeforeShutdown to 0 */
hid_set_value("ups.delay.shutdown", "0");
return STAT_INSTCMD_HANDLED;
}
/* Power on the load immediatly */
if (!strcasecmp(cmdname, "load.on")) {
/* set DelayBeforeStartup to 0 */
hid_set_value("ups.delay.start", "0");
return STAT_INSTCMD_HANDLED;
}
/* Start battery test */
if (!strcasecmp(cmdname, "test.battery.start")) {
/* set Test to 1 (Quick test) */
hid_set_value("ups.test.result", "1");
return STAT_INSTCMD_HANDLED;
}
/* Stop battery test */
if (!strcasecmp(cmdname, "test.battery.stop")) {
/* set Test to 3 (Abort test) */
hid_set_value("ups.test.result", "3");
return STAT_INSTCMD_HANDLED;
}
upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
return STAT_INSTCMD_UNKNOWN;
}
/*****************************************************************************
* shut_ups_start ()
*
* initiate communication with the UPS
*
* return 0 on success, -1 on failure
*
*****************************************************************************/
int shut_ups_start ()
{
u_char c = SHUT_SYNC, r[1];
int try;
upsdebugx (2, "entering shut_ups_start()\n");
r[0] = '\0';
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 using Complete, Off or light notification */
for (try = 0; try < MAX_TRY; try++) {
if ((shut_token_send(c)) == -1) {
upsdebugx (3, "Communication error while writing to port");
return -1;
}
serial_read (1000, &r[0]);
if (r[0] == c) {
commstatus = 1;
upsdebugx (3, "Syncing and notification setting done");
return 0;
}
}
commstatus = 0;
return -1;
}
/**********************************************************************
* shut_identify_ups ()
*
* Get SHUT device complete name
*
* return 0 on success, -1 on failure
*
*********************************************************************/
int shut_identify_ups ()
{
char string[MAX_STRING];
char model[MAX_STRING];
char *finalname = NULL;
int retcode, tries=MAX_TRY;
if (commstatus == 0)
return -1;
upsdebugx (2, "entering shut_identify_ups(0x%04x, 0x%04x)\n",
device_descriptor.dev_desc.iManufacturer,
device_descriptor.dev_desc.iProduct);
/* Get strings iModel and iProduct */
while (tries > 0)
{
if (shut_get_string(device_descriptor.dev_desc.iProduct, string, 0x25) > 0)
{
strcpy(model, string);
if(hid_get_value("UPS.PowerSummary.iModel") != 0 )
{
if((shut_get_string(hValue, string, 0x25)) > 0)
{
finalname = get_model_name(model, string);
upsdebugx (2, "iModel = %s", string);
tries = 0;
}
}
else
{
/* Try with "UPS.Flow.[4].ConfigApparentPower" */
if(hid_get_value("UPS.Flow.[4].ConfigApparentPower") != 0 )
{
sprintf(&string[0], "%i", (int)hValue);
finalname = get_model_name(model, string);
}
else
finalname = get_model_name(model, NULL);
tries = 0;
}
dstate_setinfo("ups.model", "%s", finalname);
}
else
tries--;
}
/* Get strings iSerialNumber */
if (((retcode = shut_get_string(device_descriptor.dev_desc.iSerialNumber, string, 0x25)) > 0)
&& strcmp(string, "") && string[0] != '\t') {
dstate_setinfo("ups.serial", "%s", string);
}
else
dstate_setinfo("ups.serial", "unknown");
/* all went fine */
return 1;
}
/**********************************************************************
* 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 (void)
{
u_char c[1];
c[0] = '\0';
serial_read (DEFAULT_TIMEOUT, &c[0]);
if (c[0] == SHUT_OK) {
upsdebugx (2, "shut_wait_ack(): ACK received");
return 0;
}
else if (c[0] == SHUT_NOK) {
upsdebugx (2, "shut_wait_ack(): NACK received");
return -2;
}
else if ((c[0] & SHUT_PKT_LAST) == SHUT_TYPE_NOTIFY) {
upsdebugx (2, "shut_wait_ack(): NOTIFY received");
return -3;
}
upsdebugx (2, "shut_wait_ack(): Nothing received");
return -1;
}
/**********************************************************************
* char_read (char *bytes, int size, int read_timeout)
*
* reads size bytes from the serial port
*
* bytes - buffer to store the data
* size - size of the data to get
* read_timeout - serial timeout (in milliseconds)
*
* return -1 on error, -2 on timeout, nb_bytes_readen on success
*
*********************************************************************/
static int char_read (char *bytes, int size, int read_timeout)
{
struct timeval serial_timeout;
fd_set readfs;
int readen = 0;
int rc = 0;
FD_ZERO (&readfs);
FD_SET (upsfd, &readfs);
serial_timeout.tv_usec = (read_timeout % 1000) * 1000;
serial_timeout.tv_sec = (read_timeout / 1000);
rc = select (upsfd + 1, &readfs, NULL, NULL, &serial_timeout);
if (0 == rc)
return -2; /* timeout */
if (FD_ISSET (upsfd, &readfs)) {
int now = read (upsfd, bytes, size - readen);
if (now < 0) {
return -1;
}
else {
bytes += now;
readen += now;
}
}
else {
return -1;
}
return readen;
}
/**********************************************************************
* serial_read (int read_timeout)
*
* return data one byte at a time
*
* read_timeout - serial timeout (in milliseconds)
*
* returns 0 on success, -1 on error, -2 on timeout
*
**********************************************************************/
int serial_read (int read_timeout, u_char *readbuf)
{
static u_char cache[512];
static u_char *cachep = cache;
static u_char *cachee = cache;
int recv;
*readbuf = '\0';
/* if still data in cache, get it */
if (cachep < cachee) {
*readbuf = *cachep++;
return 0;
/* return (int) *cachep++; */
}
recv = char_read ((char *)cache, 1, read_timeout);
if ((recv == -1) || (recv == -2))
return recv;
cachep = cache;
cachee = cache + recv;
cachep = cache;
cachee = cache + recv;
if (recv) {
upsdebugx(5,"received: %02x", *cachep);
*readbuf = *cachep++;
return 0;
}
return -1;
}
/**********************************************************************
* serial_send (char *buf, int len)
*
* write the content of buf to the serial port
*
* buf - data to send
* len - lenght of data to send
*
* returns number of bytes written on success, -1 on error
*
**********************************************************************/
int serial_send (u_char *buf, int len)
{
tcflush (upsfd, TCIFLUSH);
upsdebug_hex (3, "sent", (u_char *)buf, len);
return write (upsfd, buf, len);
}
/*
* Serial HID UPS Transfer (SHUT) functions
*********************************************************************/
/* Get and parse UPS status */
void shut_ups_status(void)
{
int try = 0, retcode = 0;
/* clear status buffer before begining */
status_init();
/* Ensure to have at least basic status */
while (try < MAX_TRY) {
if((retcode = hid_get_value("UPS.PowerSummary.PresentStatus.ACPresent")) != 0 ) {
try = MAX_TRY;
if(hValue == 1){
status_set("OL");
} else {
status_set("OB");
}
} else { /* retry to get data */
try++;
}
}
if(hid_get_value("UPS.PowerSummary.PresentStatus.Discharging") != 0 ) {
if(hValue == 1)
status_set("DISCHRG");
}
if(hid_get_value("UPS.PowerSummary.PresentStatus.Charging") != 0 ) {
if(hValue == 1)
status_set("CHRG");
}
if(hid_get_value("UPS.PowerSummary.PresentStatus.ShutdownImminent") != 0 ) {
if(hValue == 1)
status_set("LB");
}
if(hid_get_value("UPS.PowerSummary.PresentStatus.BelowRemainingCapacityLimit") != 0 ) {
if(hValue == 1)
status_set("LB");
}
if(hid_get_value("UPS.PowerSummary.PresentStatus.Overload") != 0 ) {
if(hValue == 1)
status_set("OVER");
}
if(hid_get_value("UPS.PowerSummary.PresentStatus.NeedReplacement") != 0 ) {
if(hValue == 1)
status_set("RB");
}
if(hid_get_value("UPS.PowerSummary.PresentStatus.Good") != 0 ) {
if(hValue == 0)
status_set("OFF");
}
/* FIXME: extend u