2013-11-24 15:00:12 +00:00
/* nutdrv_qx.c - Driver for USB and serial UPS units with Q* protocols
*
* Copyright ( C )
* 2013 Daniele Pezzini < hyouko @ gmail . com >
* Based on :
* usbhid - ups . c - Copyright ( C )
* 2003 - 2012 Arnaud Quette < arnaud . quette @ gmail . com >
* 2005 John Stamp < kinsayder @ hotmail . com >
* 2005 - 2006 Peter Selinger < selinger @ users . sourceforge . net >
* 2007 - 2009 Arjen de Korte < adkorte - guest @ alioth . debian . org >
* blazer . c - Copyright ( C )
* 2008 - 2009 Arjen de Korte < adkorte - guest @ alioth . debian . org >
* 2012 Arnaud Quette < ArnaudQuette @ Eaton . com >
* blazer_ser . c - Copyright ( C )
* 2008 Arjen de Korte < adkorte - guest @ alioth . debian . org >
* blazer_usb . c - Copyright ( C )
* 2003 - 2009 Arjen de Korte < adkorte - guest @ alioth . debian . org >
* 2011 - 2012 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
*
*/
2014-04-22 18:39:47 +00:00
# define DRIVER_VERSION "0.06"
2013-11-24 15:00:12 +00:00
# include "main.h"
# include <math.h>
/* note: QX_USB/QX_SERIAL set through Makefile */
# ifdef QX_USB
# include "libusb.h"
# include "usb-common.h"
# ifdef QX_SERIAL
# define DRIVER_NAME "Generic Q* USB / Serial driver"
# else
# define DRIVER_NAME "Generic Q* USB driver"
# endif /* QX_SERIAL */
# else
# define DRIVER_NAME "Generic Q* Serial driver"
# endif /* QX_USB */
# ifdef QX_SERIAL
# include "serial.h"
# define SER_WAIT_SEC 1 /* 3 seconds for Best UPS */
# endif /* QX_SERIAL */
# include "nutdrv_qx.h"
/* == Subdrivers == */
/* Include all known subdrivers */
# include "nutdrv_qx_mecer.h"
# include "nutdrv_qx_megatec.h"
# include "nutdrv_qx_megatec-old.h"
# include "nutdrv_qx_mustek.h"
2014-04-22 18:39:47 +00:00
# include "nutdrv_qx_q1.h"
2013-11-24 15:00:12 +00:00
# include "nutdrv_qx_voltronic.h"
2014-04-22 18:39:47 +00:00
# include "nutdrv_qx_voltronic-qs.h"
2013-11-24 15:00:12 +00:00
# include "nutdrv_qx_zinto.h"
/* Master list of avaiable subdrivers */
static subdriver_t * subdriver_list [ ] = {
& voltronic_subdriver ,
2014-04-22 18:39:47 +00:00
& voltronic_qs_subdriver ,
2013-11-24 15:00:12 +00:00
& mustek_subdriver ,
& megatec_old_subdriver ,
& mecer_subdriver ,
2014-04-22 18:39:47 +00:00
& megatec_subdriver ,
2013-11-24 15:00:12 +00:00
& zinto_subdriver ,
2014-04-22 18:39:47 +00:00
/* Fallback Q1 subdriver */
& q1_subdriver ,
2013-11-24 15:00:12 +00:00
NULL
} ;
/* == Driver description structure == */
upsdrv_info_t upsdrv_info = {
DRIVER_NAME ,
DRIVER_VERSION ,
" Daniele Pezzini <hyouko@gmail.com> " \
" Arnaud Quette <arnaud.quette@gmail.com> " \
" John Stamp <kinsayder@hotmail.com> " \
" Peter Selinger <selinger@users.sourceforge.net> " \
" Arjen de Korte <adkorte-guest@alioth.debian.org> " ,
DRV_BETA ,
# ifdef QX_USB
{ & comm_upsdrv_info , NULL }
# else
{ NULL }
# endif /* QX_USB */
} ;
/* == Data walk modes == */
typedef enum {
QX_WALKMODE_INIT = 0 ,
QX_WALKMODE_QUICK_UPDATE ,
QX_WALKMODE_FULL_UPDATE
} walkmode_t ;
/* == Global vars == */
/* Pointer to the active subdriver object (changed in subdriver_matcher() function) */
static subdriver_t * subdriver = NULL ;
static int pollfreq = DEFAULT_POLLFREQ ;
static int ups_status = 0 ;
static bool_t data_has_changed = FALSE ; /* for SEMI_STATIC data polling */
static time_t lastpoll ; /* Timestamp the last polling */
# if defined(QX_USB) && defined(QX_SERIAL)
static int is_usb = 0 ; /* Whether the device is connected through USB (1) or serial (0) */
# endif /* QX_USB && QX_SERIAL */
static struct {
char command [ SMALLBUF ] ; /* Command sent to the UPS to get answer/to execute an instant command */
char answer [ SMALLBUF ] ; /* Answer from the UPS, filled at runtime */
} previous_item = { " " , " " } ; /* Hold the values of the item processed just before the actual one */
/* == Support functions == */
static int subdriver_matcher ( void ) ;
static int qx_command ( const char * cmd , char * buf , size_t buflen ) ;
static int qx_process_answer ( item_t * item , const int len ) ;
static bool_t qx_ups_walk ( walkmode_t mode ) ;
static void ups_status_set ( void ) ;
static void ups_alarm_set ( void ) ;
static void qx_set_var ( item_t * item ) ;
/* == Struct & data for status processing == */
typedef struct {
const char * status_str ; /* UPS status string */
const int status_mask ; /* UPS status mask */
} status_lkp_t ;
static status_lkp_t status_info [ ] = {
/* Map status strings to bit masks */
{ " OL " , STATUS ( OL ) } ,
{ " LB " , STATUS ( LB ) } ,
{ " RB " , STATUS ( RB ) } ,
{ " CHRG " , STATUS ( CHRG ) } ,
{ " DISCHRG " , STATUS ( DISCHRG ) } ,
{ " BYPASS " , STATUS ( BYPASS ) } ,
{ " CAL " , STATUS ( CAL ) } ,
{ " OFF " , STATUS ( OFF ) } ,
{ " OVER " , STATUS ( OVER ) } ,
{ " TRIM " , STATUS ( TRIM ) } ,
{ " BOOST " , STATUS ( BOOST ) } ,
{ " FSD " , STATUS ( FSD ) } ,
{ NULL , 0 } ,
} ;
/* == battery.{charge,runtime} guesstimation == */
/* Support functions */
static int qx_battery ( void ) ;
static int qx_load ( void ) ;
static void qx_initbattery ( void ) ;
/* Battery data */
static struct {
double packs ; /* Battery voltage multiplier */
struct {
double act ; /* Actual runtime on battery */
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 , - 1 , 0 , 0 } , { - 1 , - 1 , - 1 , - 1 } , { - 1 , 43200 } } ;
/* Load data */
static struct {
double act ; /* Actual load (reported by the UPS) */
double low ; /* Idle load */
double eff ; /* Effective load */
} load = { 0 , 0.1 , 1 } ;
static time_t battery_lastpoll = 0 ;
/* Fill batt.volt.act and guesstimate the battery charge if it isn't already available. */
static int qx_battery ( void )
{
const char * val = dstate_getinfo ( " battery.voltage " ) ;
if ( ! val ) {
upsdebugx ( 2 , " %s: unable to get battery.voltage " , __func__ ) ;
return - 1 ;
}
batt . volt . act = batt . packs * strtod ( val , NULL ) ;
if ( batt . chrg . act = = - 1 & & 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 0 ;
}
/* Load for battery.{charge,runtime} from runtimecal */
static int qx_load ( void )
{
const char * val = dstate_getinfo ( " ups.load " ) ;
if ( ! val ) {
upsdebugx ( 2 , " %s: unable to get ups.load " , __func__ ) ;
return - 1 ;
}
load . act = strtod ( val , NULL ) ;
load . eff = pow ( load . act / 100 , batt . runt . exp ) ;
if ( load . eff < load . low ) {
load . eff = load . low ;
}
return 0 ;
}
/* Guesstimation: init */
static void qx_initbattery ( void )
{
if ( ! dstate_getinfo ( " battery.charge " ) | | ! dstate_getinfo ( " battery.runtime " ) ) {
const char * val ;
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 ) ;
}
val = dstate_getinfo ( " battery.voltage.nominal " ) ;
if ( val ) {
batt . volt . nom = strtod ( val , NULL ) ;
}
/* If no values are available for both battery.voltage.{low,high} either from the UPS or provided by the user in ups.conf, try to guesstimate them, but announce it! */
if ( batt . volt . nom ! = - 1 & & ( batt . volt . low = = - 1 | | batt . volt . high = = - 1 ) ) {
upslogx ( LOG_INFO , " No values for battery high/low voltages " ) ;
/* Basic formula, which should cover most cases */
batt . volt . low = 104 * batt . volt . nom / 120 ;
batt . volt . high = 130 * batt . volt . nom / 120 ;
/* Publish these data too */
dstate_setinfo ( " battery.voltage.low " , " %.2f " , batt . volt . low ) ;
dstate_setinfo ( " battery.voltage.high " , " %.2f " , batt . volt . high ) ;
upslogx ( LOG_INFO , " Using 'guesstimation' (low: %f, high: %f)! " , batt . volt . low , batt . volt . high ) ;
}
val = dstate_getinfo ( " battery.packs " ) ;
if ( val & & ( strspn ( val , " 0123456789 . " ) = = strlen ( val ) ) ) {
batt . packs = strtod ( val , NULL ) ;
} else {
/* qx_battery -> batt.volt.act */
if ( ! qx_battery ( ) & & batt . volt . nom ! = - 1 ) {
const double packs [ ] = { 120 , 100 , 80 , 60 , 48 , 36 , 30 , 24 , 18 , 12 , 8 , 6 , 4 , 3 , 2 , 1 , 0.5 , - 1 } ;
int i ;
/* 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 . */
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 ;
}
} else {
upslogx ( LOG_INFO , " Can't autodetect number of battery packs [%.0f/%.2f] " , batt . volt . nom , batt . volt . act ) ;
}
}
/* Update batt.{chrg,volt}.act */
qx_battery ( ) ;
val = getval ( " runtimecal " ) ;
if ( val ) {
double rh , lh , rl , ll ;
time ( & battery_lastpoll ) ;
if ( sscanf ( val , " %lf,%lf,%lf,%lf " , & 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 ( rl / rh ) / log ( lh / ll ) ;
upsdebugx ( 2 , " %s: battery runtime exponent: %.3f " , __func__ , batt . runt . exp ) ;
batt . runt . nom = rh * pow ( lh / 100 , batt . runt . exp ) ;
upsdebugx ( 2 , " %s: battery runtime nominal: %.1f " , __func__ , batt . runt . nom ) ;
} else {
upslogx ( LOG_INFO , " Battery runtime will not be calculated (runtimecal not set) " ) ;
return ;
}
val = dstate_getinfo ( " battery.charge " ) ;
if ( ! val & & batt . volt . nom ! = - 1 ) {
batt . volt . low = batt . volt . nom ;
batt . volt . high = 1.15 * batt . volt . nom ;
if ( qx_battery ( ) )
fatalx ( EXIT_FAILURE , " Initial battery charge undetermined " ) ;
val = dstate_getinfo ( " battery.charge " ) ;
}
if ( val ) {
batt . runt . est = batt . runt . nom * strtod ( val , NULL ) / 100 ;
upsdebugx ( 2 , " %s: battery runtime estimate: %.1f " , __func__ , 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 , " %s: battery charge time: %ld " , __func__ , 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 , " %s: minimum load used (idle): %.3f " , __func__ , load . low ) ;
} else {
upslogx ( LOG_INFO , " No idle load specified, using built in default [%.1f %%] " , 100 * load . low ) ;
}
}
}
/* == USB communication subdrivers == */
# if defined(QX_USB) && !defined(TESTING)
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 langid_fix = - 1 ;
static int ( * subdriver_command ) ( const char * cmd , char * buf , size_t buflen ) = NULL ;
/* Cypress communication subdriver */
static int cypress_command ( const char * cmd , char * buf , size_t buflen )
{
char tmp [ SMALLBUF ] ;
int ret ;
size_t i ;
/* Send command */
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 , 5000 ) ;
if ( ret < = 0 ) {
upsdebugx ( 3 , " send: %s " , ret ? usb_strerror ( ) : " timeout " ) ;
return ret ;
}
}
upsdebugx ( 3 , " send: %.*s " , ( int ) strcspn ( tmp , " \r " ) , tmp ) ;
/* Read reply */
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 ;
}
/* Phoenix communication subdriver */
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 ) ;
}
/* Send command */
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 ) ;
/* Read reply */
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 ;
}
/* Ippon communication subdriver */
static int ippon_command ( const char * cmd , char * buf , size_t buflen )
{
char tmp [ 64 ] ;
2014-04-22 18:39:47 +00:00
int ret , len ;
2013-11-24 15:00:12 +00:00
size_t i ;
/* Send command */
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 ;
}
2014-04-22 18:39:47 +00:00
/*
* As Ippon will always return 64 bytes in response , we have to
* calculate and return length of actual response data here .
* Empty response will look like 0x00 0x0D , otherwise it will be
* data string terminated by 0x0D .
*/
len = ( int ) strcspn ( tmp , " \r " ) ;
upsdebugx ( 3 , " read: %.*s " , len , tmp ) ;
if ( len > 0 ) {
len + + ;
}
snprintf ( buf , buflen , " %.*s " , len , tmp ) ;
return len ;
2013-11-24 15:00:12 +00:00
}
/* Krauler communication subdriver */
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 ;
if ( langid_fix ! = - 1 ) {
/* Apply langid_fix value */
ret = usb_get_string ( udev , command [ i ] . index , langid_fix , buf , buflen ) ;
}
else {
ret = usb_get_string_simple ( udev , command [ i ] . index , buf , buflen ) ;
}
if ( ret < = 0 ) {
upsdebugx ( 3 , " read: %s " , ret ? usb_strerror ( ) : " timeout " ) ;
return ret ;
}
/* This may serve in the future */
upsdebugx ( 1 , " received %d (%d) " , ret , buf [ 0 ] ) ;
if ( langid_fix ! = - 1 ) {
/* Limit this check, at least for now */
/* Invalid receive size - message corrupted */
if ( ret ! = buf [ 0 ] ) {
upsdebugx ( 1 , " size mismatch: %d / %d " , ret , buf [ 0 ] ) ;
continue ;
}
/* Simple unicode -> ASCII inplace conversion
* FIXME : this code is at least shared with mge - shut / libshut
* Create a common function ? */
unsigned int di , si , size = buf [ 0 ] ;
for ( di = 0 , si = 2 ; si < size ; si + = 2 ) {
if ( di > = ( buflen - 1 ) )
break ;
if ( buf [ si + 1 ] ) /* high byte */
buf [ di + + ] = ' ? ' ;
else
buf [ di + + ] = buf [ si ] ;
}
buf [ di ] = 0 ;
ret = di ;
}
/* "UPS No Ack" has a special meaning */
if ( ! strcasecmp ( buf , " UPS No Ack " ) ) {
upsdebugx ( 3 , " read: %.*s " , ( int ) strcspn ( buf , " \r " ) , buf ) ;
continue ;
}
/* Replace the first byte of what we received with the correct one */
buf [ 0 ] = command [ i ] . prefix ;
upsdebugx ( 3 , " read: %.*s " , ( int ) strcspn ( buf , " \r " ) , buf ) ;
return ret ;
}
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 ( USBDevice_t * device )
{
subdriver_command = & cypress_command ;
return NULL ;
}
static void * ippon_subdriver ( USBDevice_t * device )
{
subdriver_command = & ippon_command ;
return NULL ;
}
static void * krauler_subdriver ( USBDevice_t * device )
{
subdriver_command = & krauler_command ;
return NULL ;
}
static void * phoenix_subdriver ( USBDevice_t * device )
{
subdriver_command = & phoenix_command ;
return NULL ;
}
/* USB VendorID/ProductID match - note: rightmost comment is used for naming rules by tools/nut-usbinfo.pl */
static usb_device_id_t qx_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/Voltronic Power UPSes */
{ USB_DEVICE ( 0x06da , 0x0002 ) , & cypress_subdriver } , /* Online Yunto YQ450 */
{ USB_DEVICE ( 0x06da , 0x0003 ) , & ippon_subdriver } , /* Mustek Powermust */
{ USB_DEVICE ( 0x06da , 0x0004 ) , & cypress_subdriver } , /* Phoenixtec Innova 3/1 T */
{ USB_DEVICE ( 0x06da , 0x0005 ) , & cypress_subdriver } , /* Phoenixtec Innova RT */
{ USB_DEVICE ( 0x06da , 0x0201 ) , & cypress_subdriver } , /* Phoenixtec Innova T */
{ USB_DEVICE ( 0x06da , 0x0601 ) , & phoenix_subdriver } , /* Online Zinto A */
{ USB_DEVICE ( 0x0f03 , 0x0001 ) , & cypress_subdriver } , /* Unitek Alpha 1200Sx */
{ USB_DEVICE ( 0x14f0 , 0x00c9 ) , & phoenix_subdriver } , /* GE EP series */
/* 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 ( qx_usb_id , hd ) )
{
case SUPPORTED :
return 1 ;
case POSSIBLY_SUPPORTED :
case NOT_SUPPORTED :
default :
return 0 ;
}
}
static USBDeviceMatcher_t device_matcher = {
& device_match_func ,
NULL ,
NULL
} ;
# endif /* QX_USB && !TESTING */
/* == Driver functions implementations == */
/* Process instant command and take action. */
int instcmd ( const char * cmdname , const char * extradata )
{
item_t * item ;
char value [ SMALLBUF ] ;
if ( ! strcasecmp ( cmdname , " beeper.off " ) ) {
/* Compatibility mode for old command */
upslogx ( LOG_WARNING , " The 'beeper.off' command has been renamed to 'beeper.disable' " ) ;
return instcmd ( " beeper.disable " , NULL ) ;
}
if ( ! strcasecmp ( cmdname , " beeper.on " ) ) {
/* Compatibility mode for old command */
upslogx ( LOG_WARNING , " The 'beeper.on' command has been renamed to 'beeper.enable' " ) ;
return instcmd ( " beeper.enable " , NULL ) ;
}
upslogx ( LOG_INFO , " %s(%s, %s) " , __func__ , cmdname , extradata ? extradata : " [NULL] " ) ;
/* Retrieve item by command name */
item = find_nut_info ( cmdname , QX_FLAG_CMD , QX_FLAG_SKIP ) ;
/* Check for fallback if not found */
if ( item = = NULL ) {
if ( ! strcasecmp ( cmdname , " load.on " ) ) {
return instcmd ( " load.on.delay " , " 0 " ) ;
}
if ( ! strcasecmp ( cmdname , " load.off " ) ) {
return instcmd ( " load.off.delay " , " 0 " ) ;
}
if ( ! strcasecmp ( cmdname , " shutdown.return " ) ) {
int ret ;
/* Ensure "ups.start.auto" is set to "yes", if supported */
if ( dstate_getinfo ( " ups.start.auto " ) ) {
if ( setvar ( " ups.start.auto " , " yes " ) ! = STAT_SET_HANDLED ) {
upslogx ( LOG_ERR , " %s: FAILED " , __func__ ) ;
return STAT_INSTCMD_FAILED ;
}
}
ret = instcmd ( " load.on.delay " , dstate_getinfo ( " ups.delay.start " ) ) ;
if ( ret ! = STAT_INSTCMD_HANDLED ) {
return ret ;
}
return instcmd ( " load.off.delay " , dstate_getinfo ( " ups.delay.shutdown " ) ) ;
}
if ( ! strcasecmp ( cmdname , " shutdown.stayoff " ) ) {
int ret ;
/* Ensure "ups.start.auto" is set to "no", if supported */
if ( dstate_getinfo ( " ups.start.auto " ) ) {
if ( setvar ( " ups.start.auto " , " no " ) ! = STAT_SET_HANDLED ) {
upslogx ( LOG_ERR , " %s: FAILED " , __func__ ) ;
return STAT_INSTCMD_FAILED ;
}
}
ret = instcmd ( " load.on.delay " , " -1 " ) ;
if ( ret ! = STAT_INSTCMD_HANDLED ) {
return ret ;
}
return instcmd ( " load.off.delay " , dstate_getinfo ( " ups.delay.shutdown " ) ) ;
}
upsdebugx ( 2 , " %s: command %s unavailable " , __func__ , cmdname ) ;
return STAT_INSTCMD_INVALID ;
}
/* If extradata is empty, use the default value from the blazer to NUT table */
extradata = extradata ? extradata : item - > dfl ;
snprintf ( value , sizeof ( value ) , " %s " , extradata ? extradata : " " ) ;
/* Preprocess command */
if ( item - > preprocess ! = NULL & & item - > preprocess ( item , value , sizeof ( value ) ) ) {
/* Something went wrong */
upslogx ( LOG_ERR , " %s: FAILED " , __func__ ) ;
return STAT_INSTCMD_FAILED ;
}
/* No preprocess function -> nothing to do with extradata */
if ( item - > preprocess = = NULL )
snprintf ( value , sizeof ( value ) , " %s " , " " ) ;
/* Send the command, get the reply */
if ( qx_process ( item , strlen ( value ) > 0 ? value : NULL ) ) {
/* Something went wrong */
upslogx ( LOG_ERR , " %s: FAILED " , __func__ ) ;
return STAT_INSTCMD_FAILED ;
}
/* We got a reply from the UPS: either subdriver->accepted (-> command handled) or the command itself echoed back (-> command failed) */
if ( strlen ( item - > value ) > 0 ) {
if ( subdriver - > accepted ! = NULL & & ! strcasecmp ( item - > value , subdriver - > accepted ) ) {
upslogx ( LOG_INFO , " %s: SUCCEED " , __func__ ) ;
/* Set the status so that SEMI_STATIC vars are polled */
data_has_changed = TRUE ;
return STAT_INSTCMD_HANDLED ;
}
upslogx ( LOG_ERR , " %s: FAILED " , __func__ ) ;
return STAT_INSTCMD_FAILED ;
}
/* No reply from the UPS -> command handled */
upslogx ( LOG_INFO , " %s: SUCCEED " , __func__ ) ;
/* Set the status so that SEMI_STATIC vars are polled */
data_has_changed = TRUE ;
return STAT_INSTCMD_HANDLED ;
}
/* Set r/w variable to a value. */
int setvar ( const char * varname , const char * val )
{
item_t * item ;
char value [ SMALLBUF ] ;
st_tree_t * root = ( st_tree_t * ) dstate_getroot ( ) ;
int ok = 0 ;
/* Retrieve variable */
item = find_nut_info ( varname , QX_FLAG_SETVAR , QX_FLAG_SKIP ) ;
if ( item = = NULL ) {
upsdebugx ( 2 , " %s: element %s unavailable " , __func__ , varname ) ;
return STAT_SET_UNKNOWN ;
}
/* No NUT variable is available for this item, so we're handling a one-time setvar from ups.conf */
if ( item - > qxflags & QX_FLAG_NONUT ) {
const char * userval ;
/* Nothing to do */
if ( ! testvar ( item - > info_type ) ) {
upsdebugx ( 2 , " %s: nothing to do.. [%s] " , __func__ , item - > info_type ) ;
return STAT_SET_HANDLED ;
}
userval = getval ( item - > info_type ) ;
upslogx ( LOG_INFO , " %s(%s, %s) " , __func__ , varname , userval ? userval : " [NULL] " ) ;
snprintf ( value , sizeof ( value ) , " %s " , userval ? userval : " " ) ;
/* This item is available in NUT */
} else {
upslogx ( LOG_INFO , " %s(%s, %s) " , __func__ , varname , strlen ( val ) ? val : " [NULL] " ) ;
if ( ! strlen ( val ) ) {
upslogx ( LOG_ERR , " %s: value not given for %s " , __func__ , item - > info_type ) ;
return STAT_SET_UNKNOWN ; /* TODO: HANDLED but FAILED, not UNKNOWN! */
}
snprintf ( value , sizeof ( value ) , " %s " , val ) ;
/* Nothing to do */
if ( ! strcasecmp ( dstate_getinfo ( item - > info_type ) , value ) ) {
upslogx ( LOG_INFO , " %s: nothing to do.. [%s] " , __func__ , item - > info_type ) ;
return STAT_SET_HANDLED ;
}
}
/* Check if given value is in the range of accepted values (range) */
if ( item - > qxflags & QX_FLAG_RANGE ) {
int valuetoset , min , max ;
if ( strspn ( value , " 0123456789 . " ) ! = strlen ( value ) ) {
upslogx ( LOG_ERR , " %s: non numerical value [%s: %s] " , __func__ , item - > info_type , value ) ;
return STAT_SET_UNKNOWN ; /* TODO: HANDLED but FAILED, not UNKNOWN! */
}
valuetoset = strtol ( value , NULL , 10 ) ;
/* No NUT var is available for this item, so take its range from qx2nut table */
if ( item - > qxflags & QX_FLAG_NONUT ) {
info_rw_t * rvalue ;
if ( ! strlen ( value ) ) {
upslogx ( LOG_ERR , " %s: value not given for %s " , __func__ , item - > info_type ) ;
return STAT_SET_UNKNOWN ; /* TODO: HANDLED but FAILED, not UNKNOWN! */
}
min = max = - 1 ;
/* Loop on all existing values */
for ( rvalue = item - > info_rw ; rvalue ! = NULL & & strlen ( rvalue - > value ) > 0 ; rvalue + + ) {
if ( rvalue - > preprocess & & rvalue - > preprocess ( rvalue - > value , sizeof ( rvalue - > value ) ) )
continue ;
if ( min < 0 ) {
min = strtol ( rvalue - > value , NULL , 10 ) ;
continue ;
}
max = strtol ( rvalue - > value , NULL , 10 ) ;
/* valuetoset is in the range */
if ( min < = valuetoset & & valuetoset < = max ) {
ok = 1 ;
break ;
}
min = - 1 ;
max = - 1 ;
}
/* We have a NUT var for this item, so check given value against the already set range */
} else {
const range_t * range = state_getrangelist ( root , item - > info_type ) ;
/* Unable to find tree node for var */
if ( ! range ) {
upsdebugx ( 2 , " %s: unable to find tree node for %s " , __func__ , item - > info_type ) ;
return STAT_SET_UNKNOWN ;
}
while ( range ) {
min = range - > min ;
max = range - > max ;
/* valuetoset is in the range */
if ( min < = valuetoset & & valuetoset < = max ) {
ok = 1 ;
break ;
}
range = range - > next ;
}
}
if ( ! ok ) {
upslogx ( LOG_ERR , " %s: value out of range [%s: %s] " , __func__ , item - > info_type , value ) ;
return STAT_SET_UNKNOWN ; /* TODO: HANDLED but FAILED, not UNKNOWN! */
}
/* Check if given value is in the range of accepted values (enum) */
} else if ( item - > qxflags & QX_FLAG_ENUM ) {
/* No NUT var is available for this item, so take its range from qx2nut table */
if ( item - > qxflags & QX_FLAG_NONUT ) {
info_rw_t * envalue ;
if ( ! strlen ( value ) ) {
upslogx ( LOG_ERR , " %s: value not given for %s " , __func__ , item - > info_type ) ;
return STAT_SET_UNKNOWN ; /* TODO: HANDLED but FAILED, not UNKNOWN! */
}
/* Loop on all existing values */
for ( envalue = item - > info_rw ; envalue ! = NULL & & strlen ( envalue - > value ) > 0 ; envalue + + ) {
if ( envalue - > preprocess & & envalue - > preprocess ( envalue - > value , sizeof ( envalue - > value ) ) )
continue ;
if ( strcasecmp ( envalue - > value , value ) )
continue ;
/* value found */
ok = 1 ;
break ;
}
/* We have a NUT var for this item, so check given value against the already set range */
} else {
const enum_t * enumlist = state_getenumlist ( root , item - > info_type ) ;
/* Unable to find tree node for var */
if ( ! enumlist ) {
upsdebugx ( 2 , " %s: unable to find tree node for %s " , __func__ , item - > info_type ) ;
return STAT_SET_UNKNOWN ;
}
while ( enumlist ) {
/* If this is not the right value, go on to the next */
if ( strcasecmp ( enumlist - > val , value ) ) {
enumlist = enumlist - > next ;
continue ;
}
/* value found in enumlist */
ok = 1 ;
break ;
}
}
if ( ! ok ) {
upslogx ( LOG_ERR , " %s: value out of range [%s: %s] " , __func__ , item - > info_type , value ) ;
return STAT_SET_UNKNOWN ; /* TODO: HANDLED but FAILED, not UNKNOWN! */
}
/* Check if given value is not too long (string) */
} else if ( item - > info_flags & ST_FLAG_STRING ) {
const int aux = state_getaux ( root , item - > info_type ) ;
/* Unable to find tree node for var */
if ( aux < 0 ) {
upsdebugx ( 2 , " %s: unable to find tree node for %s " , __func__ , item - > info_type ) ;
return STAT_SET_UNKNOWN ;
}
if ( aux < ( int ) strlen ( value ) ) {
upslogx ( LOG_ERR , " %s: value is too long [%s: %s] " , __func__ , item - > info_type , value ) ;
return STAT_SET_UNKNOWN ; /* TODO: HANDLED but FAILED, not UNKNOWN! */
}
}
/* Preprocess value: from NUT-compliant to UPS-compliant */
if ( item - > preprocess ! = NULL & & item - > preprocess ( item , value , sizeof ( value ) ) ) {
/* Something went wrong */
upslogx ( LOG_ERR , " %s: FAILED " , __func__ ) ;
return STAT_SET_UNKNOWN ; /* TODO: HANDLED but FAILED, not UNKNOWN! */
}
/* Handle server side variable */
if ( item - > qxflags & QX_FLAG_ABSENT ) {
upsdebugx ( 2 , " %s: setting server side variable %s " , __func__ , item - > info_type ) ;
dstate_setinfo ( item - > info_type , " %s " , value ) ;
upslogx ( LOG_INFO , " %s: SUCCEED " , __func__ ) ;
return STAT_SET_HANDLED ;
}
/* No preprocess function -> nothing to do with val */
if ( item - > preprocess = = NULL )
snprintf ( value , sizeof ( value ) , " %s " , " " ) ;
/* Actual variable setting */
if ( qx_process ( item , strlen ( value ) > 0 ? value : NULL ) ) {
/* Something went wrong */
upslogx ( LOG_ERR , " %s: FAILED " , __func__ ) ;
return STAT_SET_UNKNOWN ; /* TODO: HANDLED but FAILED, not UNKNOWN! */
}
/* We got a reply from the UPS: either subdriver->accepted (-> command handled) or the command itself echoed back (-> command failed) */
if ( strlen ( item - > value ) > 0 ) {
if ( subdriver - > accepted ! = NULL & & ! strcasecmp ( item - > value , subdriver - > accepted ) ) {
upslogx ( LOG_INFO , " %s: SUCCEED " , __func__ ) ;
/* Set the status so that SEMI_STATIC vars are polled */
data_has_changed = TRUE ;
return STAT_SET_HANDLED ;
}
upslogx ( LOG_ERR , " %s: FAILED " , __func__ ) ;
return STAT_SET_UNKNOWN ; /* TODO: HANDLED but FAILED, not UNKNOWN! */
}
/* No reply from the UPS -> command handled */
upslogx ( LOG_INFO , " %s: SUCCEED " , __func__ ) ;
/* Set the status so that SEMI_STATIC vars are polled */
data_has_changed = TRUE ;
return STAT_SET_HANDLED ;
}
/* Try to shutdown the UPS */
void upsdrv_shutdown ( void )
{
int retry ;
item_t * item ;
const char * val ;
upsdebugx ( 1 , " %s... " , __func__ ) ;
/* Get user-defined delays */
/* Start delay */
item = find_nut_info ( " ups.delay.start " , 0 , QX_FLAG_SKIP ) ;
/* Don't know what happened */
if ( ! item )
fatalx ( EXIT_FAILURE , " Unable to set start delay " ) ;
/* Set the default value */
dstate_setinfo ( item - > info_type , " %s " , item - > dfl ) ;
/* Set var flags/range/enum */
qx_set_var ( item ) ;
/* Retrieve user defined delay settings */
val = getval ( QX_VAR_ONDELAY ) ;
if ( val & & setvar ( item - > info_type , val ) ! = STAT_SET_HANDLED ) {
fatalx ( EXIT_FAILURE , " Start delay '%s' out of range " , val ) ;
}
/* Shutdown delay */
item = find_nut_info ( " ups.delay.shutdown " , 0 , QX_FLAG_SKIP ) ;
/* Don't know what happened */
if ( ! item )
fatalx ( EXIT_FAILURE , " Unable to set shutdown delay " ) ;
/* Set the default value */
dstate_setinfo ( item - > info_type , " %s " , item - > dfl ) ;
/* Set var flags/range/enum */
qx_set_var ( item ) ;
/* Retrieve user defined delay settings */
val = getval ( QX_VAR_OFFDELAY ) ;
if ( val & & setvar ( item - > info_type , val ) ! = STAT_SET_HANDLED ) {
fatalx ( EXIT_FAILURE , " Shutdown delay '%s' out of range " , val ) ;
}
/* Stop pending shutdowns */
if ( find_nut_info ( " shutdown.stop " , QX_FLAG_CMD , QX_FLAG_SKIP ) ) {
for ( retry = 1 ; retry < = MAXTRIES ; retry + + ) {
if ( instcmd ( " shutdown.stop " , NULL ) ! = STAT_INSTCMD_HANDLED ) {
continue ;
}
break ;
}
if ( retry > MAXTRIES ) {
upslogx ( LOG_NOTICE , " No shutdown pending " ) ;
}
}
/* Shutdown */
for ( retry = 1 ; retry < = MAXTRIES ; retry + + ) {
if ( testvar ( " stayoff " ) ) {
if ( instcmd ( " shutdown.stayoff " , NULL ) ! = STAT_INSTCMD_HANDLED ) {
continue ;
}
} else {
if ( instcmd ( " shutdown.return " , NULL ) ! = STAT_INSTCMD_HANDLED ) {
continue ;
}
}
fatalx ( EXIT_SUCCESS , " Shutting down in %s seconds " , dstate_getinfo ( " ups.delay.shutdown " ) ) ;
}
fatalx ( EXIT_FAILURE , " Shutdown failed! " ) ;
}
void upsdrv_help ( void )
{
printf ( " Read The Fine Manual ('man 8 nutdrv_qx') \n " ) ;
}
/* Adding flags/vars */
void upsdrv_makevartable ( void )
{
char temp [ SMALLBUF ] ;
int i ;
upsdebugx ( 1 , " %s... " , __func__ ) ;
snprintf ( temp , sizeof ( temp ) , " Set shutdown delay, in seconds (default=%s) " , DEFAULT_OFFDELAY ) ;
addvar ( VAR_VALUE , QX_VAR_OFFDELAY , temp ) ;
snprintf ( temp , sizeof ( temp ) , " Set startup delay, in seconds (default=%s) " , DEFAULT_ONDELAY ) ;
addvar ( VAR_VALUE , QX_VAR_ONDELAY , temp ) ;
addvar ( VAR_FLAG , " stayoff " , " If invoked the UPS won't return after a shutdown when FSD arises " ) ;
snprintf ( temp , sizeof ( temp ) , " Set polling frequency, in seconds, to reduce data flow (default=%d) " , DEFAULT_POLLFREQ ) ;
addvar ( VAR_VALUE , QX_VAR_POLLFREQ , temp ) ;
addvar ( VAR_VALUE , " protocol " , " Preselect communication protocol (skip autodetection) " ) ;
/* battery.{charge,runtime} guesstimation */
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 " ) ;
# ifdef QX_USB
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 " ) ;
addvar ( VAR_VALUE , " langid_fix " , " Apply the language ID workaround to the krauler subdriver (0x409 or 0x4095) " ) ;
# endif /* QX_USB */
# ifdef QX_SERIAL
addvar ( VAR_VALUE , " cablepower " , " Set cable power for serial interface " ) ;
# endif /* QX_SERIAL */
/* Subdrivers flags/vars */
for ( i = 0 ; subdriver_list [ i ] ! = NULL ; i + + ) {
if ( subdriver_list [ i ] - > makevartable ! = NULL )
subdriver_list [ i ] - > makevartable ( ) ;
}
}
/* Update UPS status/infos */
void upsdrv_updateinfo ( void )
{
time_t now ;
static int retry = 0 ;
upsdebugx ( 1 , " %s... " , __func__ ) ;
time ( & now ) ;
/* Clear status buffer before beginning */
status_init ( ) ;
/* Do a full update (polling) every pollfreq or upon data change (i.e. setvar/instcmd) */
if ( ( now > ( lastpoll + pollfreq ) ) | | ( data_has_changed = = TRUE ) ) {
upsdebugx ( 1 , " Full update... " ) ;
/* Clear ups_status */
ups_status = 0 ;
alarm_init ( ) ;
if ( qx_ups_walk ( QX_WALKMODE_FULL_UPDATE ) = = FALSE ) {
if ( retry < MAXTRIES | | retry = = MAXTRIES ) {
upsdebugx ( 1 , " Communications with the UPS lost: status read failed! " ) ;
retry + + ;
} else {
dstate_datastale ( ) ;
}
return ;
}
lastpoll = now ;
data_has_changed = FALSE ;
ups_alarm_set ( ) ;
alarm_commit ( ) ;
} else {
upsdebugx ( 1 , " Quick update... " ) ;
/* Quick poll data only to see if the UPS is still connected */
if ( qx_ups_walk ( QX_WALKMODE_QUICK_UPDATE ) = = FALSE ) {
if ( retry < MAXTRIES | | retry = = MAXTRIES ) {
upsdebugx ( 1 , " Communications with the UPS lost: status read failed! " ) ;
retry + + ;
} else {
dstate_datastale ( ) ;
}
return ;
}
}
ups_status_set ( ) ;
status_commit ( ) ;
if ( retry > MAXTRIES ) {
upslogx ( LOG_NOTICE , " Communications with the UPS re-established " ) ;
}
retry = 0 ;
dstate_dataok ( ) ;
}
/* Initialise data from UPS */
void upsdrv_initinfo ( void )
{
char * val ;
upsdebugx ( 1 , " %s... " , __func__ ) ;
dstate_setinfo ( " driver.version.data " , " %s " , subdriver - > name ) ;
/* Initialise data */
if ( qx_ups_walk ( QX_WALKMODE_INIT ) = = FALSE ) {
fatalx ( EXIT_FAILURE , " Can't initialise data from the UPS " ) ;
}
/* Init battery guesstimation */
qx_initbattery ( ) ;
if ( dstate_getinfo ( " ups.delay.start " ) ) {
/* Retrieve user defined delay settings */
val = getval ( QX_VAR_ONDELAY ) ;
if ( val & & setvar ( " ups.delay.start " , val ) ! = STAT_SET_HANDLED ) {
fatalx ( EXIT_FAILURE , " Start delay '%s' out of range " , val ) ;
}
}
if ( dstate_getinfo ( " ups.delay.shutdown " ) ) {
/* Retrieve user defined delay settings */
val = getval ( QX_VAR_OFFDELAY ) ;
if ( val & & setvar ( " ups.delay.shutdown " , val ) ! = STAT_SET_HANDLED ) {
fatalx ( EXIT_FAILURE , " Shutdown delay '%s' out of range " , val ) ;
}
}
if ( ! find_nut_info ( " load.off " , QX_FLAG_CMD , QX_FLAG_SKIP ) & & find_nut_info ( " load.off.delay " , QX_FLAG_CMD , QX_FLAG_SKIP ) ) {
/* Adds default with a delay value of '0' (= immediate) */
dstate_addcmd ( " load.off " ) ;
}
if ( ! find_nut_info ( " load.on " , QX_FLAG_CMD , QX_FLAG_SKIP ) & & find_nut_info ( " load.on.delay " , QX_FLAG_CMD , QX_FLAG_SKIP ) ) {
/* Adds default with a delay value of '0' (= immediate) */
dstate_addcmd ( " load.on " ) ;
}
/* Init polling frequency */
val = getval ( QX_VAR_POLLFREQ ) ;
if ( val )
pollfreq = strtol ( val , NULL , 10 ) ;
dstate_setinfo ( " driver.parameter.pollfreq " , " %d " , pollfreq ) ;
time ( & lastpoll ) ;
/* Install handlers */
upsh . setvar = setvar ;
upsh . instcmd = instcmd ;
/* Subdriver initinfo */
if ( subdriver - > initinfo ! = NULL )
subdriver - > initinfo ( ) ;
}
/* Open the port and the like and choose the subdriver */
void upsdrv_initups ( void )
{
upsdebugx ( 1 , " %s... " , __func__ ) ;
# if defined(QX_SERIAL) && defined(QX_USB)
/* Whether the device is connected through USB or serial */
if (
! strcasecmp ( dstate_getinfo ( " driver.parameter.port " ) , " auto " ) | |
getval ( " subdriver " ) | |
getval ( " vendorid " ) | |
getval ( " productid " ) | |
getval ( " vendor " ) | |
getval ( " product " ) | |
getval ( " serial " ) | |
getval ( " bus " ) | |
getval ( " langid_fix " )
) {
/* USB */
is_usb = 1 ;
} else {
/* Serial */
is_usb = 0 ;
}
# endif /* QX_SERIAL && QX_USB */
/* Serial */
# ifdef QX_SERIAL
# ifdef QX_USB
if ( ! is_usb ) {
# endif /* QX_USB */
# ifndef TESTING
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 ;
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 /* TESTING */
# ifdef QX_USB
} else { /* is_usb */
# endif /* QX_USB */
# endif /* QX_SERIAL */
/* USB */
# ifdef QX_USB
# ifndef TESTING
const struct {
const char * name ;
int ( * command ) ( const char * cmd , char * buf , size_t buflen ) ;
} usbsubdriver [ ] = {
{ " cypress " , & cypress_command } ,
{ " phoenix " , & phoenix_command } ,
{ " ippon " , & ippon_command } ,
{ " krauler " , & krauler_command } ,
{ NULL }
} ;
int ret , langid ;
char tbuf [ 255 ] ; /* Some devices choke on size > 255 */
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 " ) ;
/* Check for language ID workaround (#1) */
if ( getval ( " langid_fix " ) ) {
/* Skip "0x" prefix and set back to hexadecimal */
if ( sscanf ( getval ( " langid_fix " ) + 2 , " %x " , & langid_fix ) ! = 1 ) {
upslogx ( LOG_NOTICE , " Error enabling language ID workaround " ) ;
} else {
upsdebugx ( 2 , " Language ID workaround enabled (using '0x%x') " , langid_fix ) ;
}
}
/* 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 ; usbsubdriver [ i ] . name ; i + + ) {
if ( strcasecmp ( subdrv , usbsubdriver [ i ] . name ) ) {
continue ;
}
subdriver_command = usbsubdriver [ 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 nutdrv_qx). \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 ) ;
/* Check for language ID workaround (#2) */
if ( langid_fix ! = - 1 ) {
/* Future improvement:
* Asking for the zero ' th index is special - it returns a string descriptor that contains all the language IDs supported by the device .
* Typically there aren ' t many - often only one .
* The language IDs are 16 bit numbers , and they start at the third byte in the descriptor .
* See USB 2.0 specification , section 9.6 .7 , for more information on this .
* This should allow automatic application of the workaround */
ret = usb_get_string ( udev , 0 , 0 , tbuf , sizeof ( tbuf ) ) ;
if ( ret > = 4 ) {
langid = tbuf [ 2 ] | ( tbuf [ 3 ] < < 8 ) ;
upsdebugx ( 1 , " First supported language ID: 0x%x (please report to the NUT maintainer!) " , langid ) ;
}
}
# endif /* TESTING */
# ifdef QX_SERIAL
} /* is_usb */
# endif /* QX_SERIAL */
# endif /* QX_USB */
/* Choose subdriver */
if ( ! subdriver_matcher ( ) )
fatalx ( EXIT_FAILURE , " Device not supported! " ) ;
/* Subdriver initups */
if ( subdriver - > initups ! = NULL )
subdriver - > initups ( ) ;
}
/* Close the ports and the like */
void upsdrv_cleanup ( void )
{
upsdebugx ( 1 , " %s... " , __func__ ) ;
# ifndef TESTING
# ifdef QX_SERIAL
# ifdef QX_USB
if ( ! is_usb ) {
# endif /* QX_USB */
ser_set_dtr ( upsfd , 0 ) ;
ser_close ( upsfd , device_path ) ;
# ifdef QX_USB
} else { /* is_usb */
# endif /* QX_USB */
# endif /* QX_SERIAL */
# ifdef QX_USB
usb - > close ( udev ) ;
USBFreeExactMatcher ( reopen_matcher ) ;
USBFreeRegexMatcher ( regex_matcher ) ;
free ( usbdevice . Vendor ) ;
free ( usbdevice . Product ) ;
free ( usbdevice . Serial ) ;
free ( usbdevice . Bus ) ;
# ifdef QX_SERIAL
} /* is_usb */
# endif /* QX_SERIAL */
# endif /* QX_USB */
# endif /* TESTING */
}
/* == Support functions == */
/* 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 . */
static int qx_command ( const char * cmd , char * buf , size_t buflen )
{
# ifndef TESTING
int ret = - 1 ;
# ifdef QX_USB
# ifdef QX_SERIAL
/* Communication: USB */
if ( is_usb ) {
# endif /* QX_SERIAL */
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 /* ETIME */
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 */
2014-04-22 18:39:47 +00:00
# ifdef EPROTO
2013-11-24 15:00:12 +00:00
case - EPROTO : /* Protocol error */
2014-04-22 18:39:47 +00:00
# endif
2013-11-24 15:00:12 +00:00
default :
break ;
}
# ifdef QX_SERIAL
/* Communication: serial */
} else { /* !is_usb */
# endif /* QX_SERIAL */
# endif /* QX_USB */
# ifdef QX_SERIAL
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 ) ;
# ifdef QX_USB
} /* !is_usb */
# endif /* QX_USB */
# endif /* QX_SERIAL */
return ret ;
# else /* TESTING */
testing_t * testing = subdriver - > testing ;
int i ;
memset ( buf , 0 , buflen ) ;
upsdebugx ( 3 , " send: '%.*s' " , ( int ) strcspn ( cmd , " \r " ) , cmd ) ;
for ( i = 0 ; cmd & & testing [ i ] . cmd ; i + + ) {
if ( strcasecmp ( cmd , testing [ i ] . cmd ) ) {
continue ;
}
upsdebugx ( 3 , " read: '%.*s' " , ( int ) strcspn ( testing [ i ] . answer , " \r " ) , testing [ i ] . answer ) ;
return snprintf ( buf , buflen , " %s " , testing [ i ] . answer ) ;
}
/* If the driver expects some kind of reply in case of error.. */
if ( subdriver - > rejected ! = NULL ) {
/* ..fulfill its expectations.. */
upsdebugx ( 3 , " read: '%.*s' " , ( int ) strcspn ( subdriver - > rejected , " \r " ) , subdriver - > rejected ) ;
return snprintf ( buf , buflen , " %s " , subdriver - > rejected ) ;
/* ..otherwise.. */
} else {
/* ..echo back the command */
upsdebugx ( 3 , " read: '%.*s' " , ( int ) strcspn ( cmd , " \r " ) , cmd ) ;
return snprintf ( buf , buflen , " %s " , cmd ) ;
}
# endif /* TESTING */
}
/* Update ups_status to remember this status item.
* Interpretation is done in ups_status_set ( ) . */
void update_status ( const char * value )
{
status_lkp_t * status_item ;
int clear = 0 ;
upsdebugx ( 5 , " %s: %s " , __func__ , value ) ;
if ( * value = = ' ! ' ) {
value + + ;
clear = 1 ;
}
for ( status_item = status_info ; status_item - > status_str ! = NULL ; status_item + + ) {
if ( strcasecmp ( status_item - > status_str , value ) )
continue ;
if ( clear ) {
ups_status & = ~ status_item - > status_mask ;
} else {
ups_status | = status_item - > status_mask ;
}
return ;
}
upsdebugx ( 5 , " %s: Warning! %s not in list of known values " , __func__ , value ) ;
}
/* Choose subdriver */
static int subdriver_matcher ( void )
{
const char * protocol = getval ( " protocol " ) ;
int i ;
/* Select the subdriver for this device */
for ( i = 0 ; subdriver_list [ i ] ! = NULL ; i + + ) {
int j ;
/* If protocol is set in ups.conf, use it */
if ( protocol ) {
char subdrv_name [ SMALLBUF ] ;
/* Get rid of subdriver version */
snprintf ( subdrv_name , sizeof ( subdrv_name ) , " %.*s " , ( int ) strcspn ( subdriver_list [ i ] - > name , " " ) , subdriver_list [ i ] - > name ) ;
if ( strcasecmp ( subdrv_name , protocol ) ) {
upsdebugx ( 2 , " Skipping protocol %s " , subdriver_list [ i ] - > name ) ;
continue ;
}
}
/* Give every subdriver some tries */
for ( j = 0 ; j < MAXTRIES ; j + + ) {
subdriver = subdriver_list [ i ] ;
if ( subdriver - > claim ( ) ) {
break ;
}
subdriver = NULL ;
}
if ( subdriver ! = NULL )
break ;
}
if ( ! subdriver ) {
upslogx ( LOG_ERR , " Device not supported! " ) ;
return 0 ;
}
upslogx ( LOG_INFO , " Using protocol: %s " , subdriver - > name ) ;
return 1 ;
}
/* Set vars boundaries */
static void qx_set_var ( item_t * item )
{
if ( ! ( item - > qxflags & QX_FLAG_NONUT ) )
dstate_setflags ( item - > info_type , item - > info_flags ) ;
/* Set max length for strings, if needed */
if ( item - > info_flags & ST_FLAG_STRING & & ! ( item - > qxflags & QX_FLAG_NONUT ) )
dstate_setaux ( item - > info_type , strtol ( item - > info_rw [ 0 ] . value , NULL , 10 ) ) ;
/* Set enum list */
if ( item - > qxflags & QX_FLAG_ENUM ) {
info_rw_t * envalue ;
char buf [ LARGEBUF ] = " " ;
/* Loop on all existing values */
for ( envalue = item - > info_rw ; envalue ! = NULL & & strlen ( envalue - > value ) > 0 ; envalue + + ) {
if ( envalue - > preprocess & & envalue - > preprocess ( envalue - > value , sizeof ( envalue - > value ) ) )
continue ;
/* This item is not available yet in NUT, so publish these data in the logs */
if ( item - > qxflags & QX_FLAG_NONUT ) {
snprintfcat ( buf , sizeof ( buf ) , " %s " , envalue - > value ) ;
/* This item is available in NUT, add its enum to the variable */
} else {
dstate_addenum ( item - > info_type , " %s " , envalue - > value ) ;
}
}
if ( item - > qxflags & QX_FLAG_NONUT )
upslogx ( LOG_INFO , " %s, settable values:%s " , item - > info_type , strlen ( buf ) > 0 ? buf : " none " ) ;
}
/* Set range */
if ( item - > qxflags & QX_FLAG_RANGE ) {
info_rw_t * rvalue , * from = NULL , * to = NULL ;
int ok = 0 ;
/* Loop on all existing values */
for ( rvalue = item - > info_rw ; rvalue ! = NULL & & strlen ( rvalue - > value ) > 0 ; rvalue + + ) {
if ( rvalue - > preprocess & & rvalue - > preprocess ( rvalue - > value , sizeof ( rvalue - > value ) ) )
continue ;
if ( ! from ) {
from = rvalue ;
continue ;
}
to = rvalue ;
/* This item is not available yet in NUT, so publish these data in the logs */
if ( item - > qxflags & QX_FLAG_NONUT ) {
upslogx ( LOG_INFO , " %s, settable range: %s..%s " , item - > info_type , from - > value , to - > value ) ;
ok + + ;
/* This item is available in NUT, add its range to the variable */
} else {
dstate_addrange ( item - > info_type , strtol ( from - > value , NULL , 10 ) , strtol ( to - > value , NULL , 10 ) ) ;
}
from = NULL ;
to = NULL ;
}
/* This item is not available yet in NUT and we weren't able to get its range; let people know it */
if ( ( item - > qxflags & QX_FLAG_NONUT ) & & ! ok )
upslogx ( LOG_INFO , " %s, settable range: none " , item - > info_type ) ;
}
}
/* Walk UPS variables and set elements of the qx2nut array. */
static bool_t qx_ups_walk ( walkmode_t mode )
{
item_t * item ;
int retcode ;
/* Clear batt.{chrg,runt}.act for guesstimation */
if ( mode = = QX_WALKMODE_FULL_UPDATE ) {
batt . runt . act = - 1 ;
batt . chrg . act = - 1 ;
}
/* 3 modes: QX_WALKMODE_INIT, QX_WALKMODE_QUICK_UPDATE and QX_WALKMODE_FULL_UPDATE */
/* Device data walk */
for ( item = subdriver - > qx2nut ; item - > info_type ! = NULL ; item + + ) {
/* Skip this item */
if ( item - > qxflags & QX_FLAG_SKIP )
continue ;
upsdebugx ( 10 , " %s: processing: %s " , __func__ , item - > info_type ) ;
/* Filter data according to mode */
switch ( mode )
{
/* Device capabilities enumeration */
case QX_WALKMODE_INIT :
/* Special case for handling server side variables */
if ( item - > qxflags & QX_FLAG_ABSENT ) {
/* Already set */
if ( dstate_getinfo ( item - > info_type ) )
continue ;
dstate_setinfo ( item - > info_type , " %s " , item - > dfl ) ;
/* Set var flags/range/enum */
qx_set_var ( item ) ;
continue ;
}
/* Allow duplicates for these NUT variables */
if ( ! strncmp ( item - > info_type , " ups.alarm " , 9 ) | | ! strncmp ( item - > info_type , " ups.status " , 10 ) )
break ;
/* This one doesn't exist yet */
if ( dstate_getinfo ( item - > info_type ) = = NULL )
break ;
continue ;
case QX_WALKMODE_QUICK_UPDATE :
/* Quick update only deals with status and alarms! */
if ( ! ( item - > qxflags & QX_FLAG_QUICK_POLL ) )
continue ;
break ;
case QX_WALKMODE_FULL_UPDATE :
/* These don't need polling after initinfo() */
if ( item - > qxflags & ( QX_FLAG_ABSENT | QX_FLAG_CMD | QX_FLAG_SETVAR | QX_FLAG_STATIC ) )
continue ;
/* These need to be polled after user changes (setvar / instcmd) */
if ( ( item - > qxflags & QX_FLAG_SEMI_STATIC ) & & ( data_has_changed = = FALSE ) )
continue ;
break ;
default :
fatalx ( EXIT_FAILURE , " %s: unknown update mode! " , __func__ ) ;
}
/* Instant commands */
if ( item - > qxflags & QX_FLAG_CMD ) {
dstate_addcmd ( item - > info_type ) ;
continue ;
}
/* Setvars */
if ( item - > qxflags & QX_FLAG_SETVAR ) {
if ( item - > qxflags & QX_FLAG_NONUT ) {
setvar ( item - > info_type , NULL ) ;
item - > qxflags | = QX_FLAG_SKIP ;
}
continue ;
}
/* Check whether the previous item uses the same command and then use its answer, if available.. */
if ( strlen ( previous_item . command ) > 0 & & strlen ( previous_item . answer ) > 0 & & ! strcasecmp ( previous_item . command , item - > command ) ) {
snprintf ( item - > answer , sizeof ( item - > answer ) , " %s " , previous_item . answer ) ;
/* Process the answer */
retcode = qx_process_answer ( item , strlen ( item - > answer ) ) ;
/* ..otherwise: execute command to get answer from the UPS */
} else
retcode = qx_process ( item , NULL ) ;
/* Record item as previous_item */
snprintf ( previous_item . command , sizeof ( previous_item . command ) , " %s " , item - > command ) ;
snprintf ( previous_item . answer , sizeof ( previous_item . answer ) , " %s " , item - > answer ) ;
if ( retcode ) {
if ( item - > qxflags & QX_FLAG_QUICK_POLL )
return FALSE ;
if ( mode = = QX_WALKMODE_INIT )
/* Skip this item from now on */
item - > qxflags | = QX_FLAG_SKIP ;
/* Clear data from the item */
snprintf ( item - > answer , sizeof ( item - > answer ) , " %s " , " " ) ;
snprintf ( item - > value , sizeof ( item - > value ) , " %s " , " " ) ;
/* Don't know what happened, try again later... */
continue ;
}
/* Process the value we got back (set status bits and set the value of other parameters) */
retcode = ups_infoval_set ( item ) ;
/* Clear data from the item */
snprintf ( item - > answer , sizeof ( item - > answer ) , " %s " , " " ) ;
snprintf ( item - > value , sizeof ( item - > value ) , " %s " , " " ) ;
/* Uh-oh! Some error! */
if ( retcode = = - 1 )
continue ;
/* Set var flags/range/enum (not for ups.{alarm.status}, hence the retcode check) */
if ( retcode & & mode = = QX_WALKMODE_INIT ) {
qx_set_var ( item ) ;
}
}
/* Clear data from previous_item */
snprintf ( previous_item . command , sizeof ( previous_item . command ) , " %s " , " " ) ;
snprintf ( previous_item . answer , sizeof ( previous_item . answer ) , " %s " , " " ) ;
/* Update battery guesstimation */
if ( mode = = QX_WALKMODE_FULL_UPDATE & & ( batt . runt . act = = - 1 | | batt . chrg . act = = - 1 ) ) {
if ( getval ( " runtimecal " ) ) {
time_t battery_now ;
time ( & battery_now ) ;
/* OL */
if ( ups_status & STATUS ( OL ) ) {
batt . runt . est + = batt . runt . nom * difftime ( battery_now , battery_lastpoll ) / batt . chrg . time ;
if ( batt . runt . est > batt . runt . nom ) {
batt . runt . est = batt . runt . nom ;
}
/* OB */
} else {
batt . runt . est - = load . eff * difftime ( battery_now , battery_lastpoll ) ;
if ( batt . runt . est < 0 ) {
batt . runt . est = 0 ;
}
}
if ( batt . chrg . act = = - 1 )
dstate_setinfo ( " battery.charge " , " %.0f " , 100 * batt . runt . est / batt . runt . nom ) ;
if ( batt . runt . act = = - 1 & & ! qx_load ( ) )
dstate_setinfo ( " battery.runtime " , " %.0f " , batt . runt . est / load . eff ) ;
battery_lastpoll = battery_now ;
} else {
qx_battery ( ) ;
}
}
return TRUE ;
}
/* Convert the local status information to NUT format and set NUT alarms. */
static void ups_alarm_set ( void )
{
if ( ups_status & STATUS ( RB ) ) {
alarm_set ( " Replace battery! " ) ;
}
if ( ups_status & STATUS ( FSD ) ) {
alarm_set ( " Shutdown imminent! " ) ;
}
}
/* Convert the local status information to NUT format and set NUT status. */
static void ups_status_set ( void )
{
if ( ups_status & STATUS ( OL ) ) {
status_set ( " OL " ) ; /* On line */
} else {
status_set ( " OB " ) ; /* On battery */
}
if ( ups_status & STATUS ( DISCHRG ) ) {
status_set ( " DISCHRG " ) ; /* Discharging */
}
if ( ups_status & STATUS ( CHRG ) ) {
status_set ( " CHRG " ) ; /* Charging */
}
if ( ups_status & STATUS ( LB ) ) {
status_set ( " LB " ) ; /* Low battery */
}
if ( ups_status & STATUS ( OVER ) ) {
status_set ( " OVER " ) ; /* Overload */
}
if ( ups_status & STATUS ( RB ) ) {
status_set ( " RB " ) ; /* Replace battery */
}
if ( ups_status & STATUS ( TRIM ) ) {
status_set ( " TRIM " ) ; /* SmartTrim */
}
if ( ups_status & STATUS ( BOOST ) ) {
status_set ( " BOOST " ) ; /* SmartBoost */
}
if ( ups_status & STATUS ( BYPASS ) ) {
status_set ( " BYPASS " ) ; /* On bypass */
}
if ( ups_status & STATUS ( OFF ) ) {
status_set ( " OFF " ) ; /* UPS is off */
}
if ( ups_status & STATUS ( CAL ) ) {
status_set ( " CAL " ) ; /* Calibration */
}
if ( ups_status & STATUS ( FSD ) ) {
status_set ( " FSD " ) ; /* Forced shutdown */
}
}
/* Find element definition in qx2nut array by NUT varname optionally filtered by its qxflags:
* - ' flag ' : flags that have to be set in the item , i . e . if one of the flags is absent in the item it won ' t be returned
* - ' noflag ' : flags that have to be absent in the item , i . e . if at least one of the flags is set in the item it won ' t be returned */
item_t * find_nut_info ( const char * varname , const unsigned long flag , const unsigned long noflag )
{
item_t * item ;
for ( item = subdriver - > qx2nut ; item - > info_type ! = NULL ; item + + ) {
if ( strcasecmp ( item - > info_type , varname ) )
continue ;
if ( flag & & ( ( item - > qxflags & flag ) ! = flag ) )
continue ;
if ( noflag & & ( item - > qxflags & noflag ) )
continue ;
return item ;
}
upsdebugx ( 2 , " %s: info type %s not found " , __func__ , varname ) ;
return NULL ;
}
/* Process the answer we got back from the UPS
* Return - 1 on errors , 0 on success */
static int qx_process_answer ( item_t * item , const int len )
{
/* Query rejected by the UPS */
if ( subdriver - > rejected & & ! strcasecmp ( item - > answer , subdriver - > rejected ) ) {
upsdebugx ( 2 , " %s: query rejected by the UPS (%s) " , __func__ , item - > info_type ) ;
return - 1 ;
}
/* Short reply */
if ( item - > answer_len & & len < item - > answer_len ) {
upsdebugx ( 2 , " %s: short reply (%s) " , __func__ , item - > info_type ) ;
return - 1 ;
}
/* Wrong leading character */
if ( item - > leading & & item - > answer [ 0 ] ! = item - > leading ) {
upsdebugx ( 2 , " %s: %s - invalid start character [%02x], expected [%02x] " , __func__ , item - > info_type , item - > answer [ 0 ] , item - > leading ) ;
return - 1 ;
}
/* Get value */
if ( strlen ( item - > answer ) ) {
snprintf ( item - > value , sizeof ( item - > value ) , " %.*s " , item - > to ? 1 + item - > to - item - > from : ( int ) strcspn ( item - > answer , " \r " ) - item - > from , item - > answer + item - > from ) ;
} else {
snprintf ( item - > value , sizeof ( item - > value ) , " %s " , " " ) ;
}
return 0 ;
}
/* Send the command to the UPS and process the reply.
* Return - 1 on errors , 0 on success */
int qx_process ( item_t * item , const char * command )
{
char buf [ SMALLBUF ] = " " ;
/* Send the command */
int len = qx_command ( command ? command : item - > command , buf , sizeof ( buf ) ) ;
snprintf ( item - > answer , sizeof ( item - > answer ) , " %s " , buf ) ;
/* Process the answer to get the value */
return qx_process_answer ( item , len ) ;
}
/* Process the value we got back (set status bits and set the value of other parameters). */
/* Return -1 on failure, 0 for a status update and 1 in all other cases */
int ups_infoval_set ( item_t * item )
{
char value [ SMALLBUF ] = " " ;
/* Item need to be preprocessed? */
if ( item - > preprocess ! = NULL ) {
/* Process the value returned by the UPS to NUT standards */
if ( item - > preprocess ( item , value , sizeof ( value ) ) ) {
upsdebugx ( 4 , " %s: failed to preprocess value [%s: %s] " , __func__ , item - > info_type , item - > value ) ;
return - 1 ;
}
/* Deal with status items */
if ( ! strncmp ( item - > info_type , " ups.status " , 10 ) ) {
if ( strlen ( value ) > 0 )
update_status ( value ) ;
return 0 ;
}
/* Deal with alarm items */
if ( ! strncmp ( item - > info_type , " ups.alarm " , 9 ) ) {
if ( strlen ( value ) > 0 )
alarm_set ( value ) ;
return 0 ;
}
} else {
snprintf ( value , sizeof ( value ) , " %s " , item - > value ) ;
/* Cover most of the cases: either left/right filled with hashes, spaces or a mix of both */
if ( item - > qxflags & QX_FLAG_TRIM ) {
char buf [ SMALLBUF ] ;
snprintf ( buf , sizeof ( buf ) , " %s " , ltrim ( value , ' ' ) ) ;
snprintf ( value , sizeof ( value ) , " %s " , buf ) ;
snprintf ( buf , sizeof ( buf ) , " %s " , rtrim ( value , ' ' ) ) ;
snprintf ( value , sizeof ( value ) , " %s " , buf ) ;
snprintf ( buf , sizeof ( buf ) , " %s " , ltrim ( value , ' # ' ) ) ;
snprintf ( value , sizeof ( value ) , " %s " , buf ) ;
snprintf ( buf , sizeof ( buf ) , " %s " , rtrim ( value , ' # ' ) ) ;
snprintf ( value , sizeof ( value ) , " %s " , buf ) ;
snprintf ( buf , sizeof ( buf ) , " %s " , ltrim ( value , ' ' ) ) ;
snprintf ( value , sizeof ( value ) , " %s " , buf ) ;
snprintf ( buf , sizeof ( buf ) , " %s " , rtrim ( value , ' ' ) ) ;
snprintf ( value , sizeof ( value ) , " %s " , buf ) ;
snprintf ( buf , sizeof ( buf ) , " %s " , ltrim ( value , ' # ' ) ) ;
snprintf ( value , sizeof ( value ) , " %s " , buf ) ;
snprintf ( buf , sizeof ( buf ) , " %s " , rtrim ( value , ' # ' ) ) ;
snprintf ( value , sizeof ( value ) , " %s " , buf ) ;
}
if ( strcasecmp ( item - > dfl , " %s " ) ) {
if ( strspn ( value , " 0123456789 . " ) ! = strlen ( value ) ) {
upsdebugx ( 2 , " %s: non numerical value [%s: %s] " , __func__ , item - > info_type , value ) ;
return - 1 ;
}
snprintf ( value , sizeof ( value ) , item - > dfl , strtod ( value , NULL ) ) ;
}
}
if ( item - > qxflags & QX_FLAG_NONUT ) {
upslogx ( LOG_INFO , " %s: %s " , item - > info_type , value ) ;
return 1 ;
}
if ( ! strlen ( value ) ) {
upsdebugx ( 1 , " %s: non significant value [%s] " , __func__ , item - > info_type ) ;
return - 1 ;
}
dstate_setinfo ( item - > info_type , " %s " , value ) ;
/* Fill batt.{chrg,runt}.act for guesstimation */
if ( ! strcasecmp ( item - > info_type , " battery.charge " ) )
batt . chrg . act = strtol ( value , NULL , 10 ) ;
else if ( ! strcasecmp ( item - > info_type , " battery.runtime " ) )
batt . runt . act = strtol ( value , NULL , 10 ) ;
return 1 ;
}
/* Return actual status */
int qx_status ( void )
{
return ups_status ;
}