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 >
2022-06-29 10:37:36 +00:00
* 2016 Eaton
2013-11-24 15:00:12 +00:00
* 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 >
2022-06-29 10:37:36 +00:00
* Masterguard additions
* 2020 - 2021 Edgar Fuß , Mathematisches Institut der Universität Bonn < ef @ math . uni - bonn . de >
* Armac ( Richcomm - variant ) additions
* 2021 Tomasz Fortuna < bla @ thera . be >
2013-11-24 15:00:12 +00:00
*
* 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
*
*/
2022-06-29 10:37:36 +00:00
# define DRIVER_VERSION "0.32"
2013-11-24 15:00:12 +00:00
2022-06-29 10:37:36 +00:00
# include "config.h"
2013-11-24 15:00:12 +00:00
# include "main.h"
2022-06-29 10:37:36 +00:00
# include "attribute.h"
# include "nut_float.h"
# include "nut_stdint.h"
2013-11-24 15:00:12 +00:00
/* note: QX_USB/QX_SERIAL set through Makefile */
# ifdef QX_USB
2022-06-29 10:37:36 +00:00
# include "nut_libusb.h" /* also includes "usb-common.h" */
2013-11-24 15:00:12 +00:00
# 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 */
2015-04-30 13:53:36 +00:00
# include "nutdrv_qx_bestups.h"
2022-06-29 10:37:36 +00:00
# include "nutdrv_qx_hunnox.h"
2013-11-24 15:00:12 +00:00
# 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"
2015-04-30 13:53:36 +00:00
# include "nutdrv_qx_voltronic-qs-hex.h"
2013-11-24 15:00:12 +00:00
# include "nutdrv_qx_zinto.h"
2022-06-29 10:37:36 +00:00
# include "nutdrv_qx_masterguard.h"
# include "nutdrv_qx_ablerex.h"
2013-11-24 15:00:12 +00:00
2022-06-29 10:37:36 +00:00
/* Reference list of available non-USB subdrivers */
2013-11-24 15:00:12 +00:00
static subdriver_t * subdriver_list [ ] = {
& voltronic_subdriver ,
2014-04-22 18:39:47 +00:00
& voltronic_qs_subdriver ,
2015-04-30 13:53:36 +00:00
& voltronic_qs_hex_subdriver ,
2013-11-24 15:00:12 +00:00
& mustek_subdriver ,
& megatec_old_subdriver ,
2015-04-30 13:53:36 +00:00
& bestups_subdriver ,
2013-11-24 15:00:12 +00:00
& mecer_subdriver ,
2014-04-22 18:39:47 +00:00
& megatec_subdriver ,
2013-11-24 15:00:12 +00:00
& zinto_subdriver ,
2022-06-29 10:37:36 +00:00
& masterguard_subdriver ,
& hunnox_subdriver ,
& ablerex_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> " \
2022-06-29 10:37:36 +00:00
" Arjen de Korte <adkorte-guest@alioth.debian.org> " \
" Edgar Fuß <ef@math.uni-bonn.de> " ,
2013-11-24 15:00:12 +00:00
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 ;
2022-06-29 10:37:36 +00:00
static long pollfreq = DEFAULT_POLLFREQ ;
static unsigned int ups_status = 0 ;
2013-11-24 15:00:12 +00:00
static bool_t data_has_changed = FALSE ; /* for SEMI_STATIC data polling */
static time_t lastpoll ; /* Timestamp the last polling */
2022-06-29 10:37:36 +00:00
# if defined(QX_USB) && !defined(TESTING)
static int hunnox_step = 0 ;
# endif /* QX_USB && !TESTING */
2013-11-24 15:00:12 +00:00
# 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 ) ;
2022-06-29 10:37:36 +00:00
static ssize_t qx_command ( const char * cmd , char * buf , size_t buflen ) ;
static int qx_process_answer ( item_t * item , const size_t len ) ; /* returns just 0 or -1 */
2013-11-24 15:00:12 +00:00
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 {
2022-06-29 10:37:36 +00:00
const char * status_str ; /* UPS status string */
const unsigned int status_mask ; /* UPS status mask */
2013-11-24 15:00:12 +00:00
} 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 ;
2022-06-29 10:37:36 +00:00
/* Fill batt.volt.act and guesstimate the battery charge
* if it isn ' t already available . */
2013-11-24 15:00:12 +00:00
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 ) ;
2022-06-29 10:37:36 +00:00
if ( d_equal ( batt . chrg . act , - 1 ) & & batt . volt . low > 0 & & batt . volt . high > batt . volt . low ) {
2013-11-24 15:00:12 +00:00
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 ) ;
}
2022-06-29 10:37:36 +00:00
/* 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 ( ( ! d_equal ( batt . volt . nom , - 1 ) ) & & ( d_equal ( batt . volt . low , - 1 ) | | d_equal ( batt . volt . high , - 1 ) ) ) {
2013-11-24 15:00:12 +00:00
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 ) ;
2022-06-29 10:37:36 +00:00
upslogx ( LOG_INFO , " Using 'guesstimation' (low: %f, high: %f)! " ,
batt . volt . low , batt . volt . high ) ;
2013-11-24 15:00:12 +00:00
}
val = dstate_getinfo ( " battery.packs " ) ;
if ( val & & ( strspn ( val , " 0123456789 . " ) = = strlen ( val ) ) ) {
batt . packs = strtod ( val , NULL ) ;
} else {
/* qx_battery -> batt.volt.act */
2022-06-29 10:37:36 +00:00
if ( ! qx_battery ( ) & & ( ! d_equal ( batt . volt . nom , - 1 ) ) ) {
2013-11-24 15:00:12 +00:00
const double packs [ ] = { 120 , 100 , 80 , 60 , 48 , 36 , 30 , 24 , 18 , 12 , 8 , 6 , 4 , 3 , 2 , 1 , 0.5 , - 1 } ;
int i ;
2022-06-29 10:37:36 +00:00
/* The battery voltage will quickly return to
* at least the nominal value after discharging them .
* For overlapping battery . voltage . low / high ranges
* therefore choose the one with the highest multiplier . */
2013-11-24 15:00:12 +00:00
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 ) {
2022-06-29 10:37:36 +00:00
upslogx ( LOG_INFO ,
" Can't autodetect number of battery packs [%.0f/%.2f] " ,
batt . volt . nom , batt . volt . act ) ;
2013-11-24 15:00:12 +00:00
break ;
}
batt . packs = packs [ i ] ;
break ;
}
} else {
2022-06-29 10:37:36 +00:00
upslogx ( LOG_INFO ,
" Can't autodetect number of battery packs [%.0f/%.2f] " ,
batt . volt . nom , batt . volt . act ) ;
2013-11-24 15:00:12 +00:00
}
}
/* 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 ) ;
2022-06-29 10:37:36 +00:00
upsdebugx ( 2 , " %s: battery runtime exponent: %.3f " ,
__func__ , batt . runt . exp ) ;
2013-11-24 15:00:12 +00:00
batt . runt . nom = rh * pow ( lh / 100 , batt . runt . exp ) ;
2022-06-29 10:37:36 +00:00
upsdebugx ( 2 , " %s: battery runtime nominal: %.1f " ,
__func__ , batt . runt . nom ) ;
2013-11-24 15:00:12 +00:00
} else {
2022-06-29 10:37:36 +00:00
upslogx ( LOG_INFO , " Battery runtime will not be calculated "
" (runtimecal not set) " ) ;
2013-11-24 15:00:12 +00:00
return ;
}
val = dstate_getinfo ( " battery.charge " ) ;
2022-06-29 10:37:36 +00:00
if ( ! val & & ( ! d_equal ( batt . volt . nom , - 1 ) ) ) {
2013-11-24 15:00:12 +00:00
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 ;
2022-06-29 10:37:36 +00:00
upsdebugx ( 2 , " %s: battery runtime estimate: %.1f " ,
__func__ , batt . runt . est ) ;
2013-11-24 15:00:12 +00:00
} 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] " ) ;
}
2022-06-29 10:37:36 +00:00
upsdebugx ( 2 , " %s: battery charge time: %ld " ,
__func__ , batt . chrg . time ) ;
2013-11-24 15:00:12 +00:00
} else {
2022-06-29 10:37:36 +00:00
upslogx ( LOG_INFO ,
" No charge time specified, "
" using built in default [%ld seconds] " ,
batt . chrg . time ) ;
2013-11-24 15:00:12 +00:00
}
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] " ) ;
}
2022-06-29 10:37:36 +00:00
upsdebugx ( 2 ,
" %s: minimum load used (idle): %.3f " ,
__func__ , load . low ) ;
2013-11-24 15:00:12 +00:00
} else {
2022-06-29 10:37:36 +00:00
upslogx ( LOG_INFO ,
" No idle load specified, using built in default [%.1f %%] " ,
100 * load . low ) ;
2013-11-24 15:00:12 +00:00
}
}
}
/* == 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 ] ;
2022-06-29 10:37:36 +00:00
int ret = 0 ;
2013-11-24 15:00:12 +00:00
size_t i ;
2022-06-29 10:37:36 +00:00
if ( buflen > INT_MAX ) {
upsdebugx ( 3 , " %s: requested to read too much (%zu), "
" reducing buflen to (INT_MAX-1) " ,
__func__ , buflen ) ;
buflen = ( INT_MAX - 1 ) ;
}
2013-11-24 15:00:12 +00:00
/* Send command */
memset ( tmp , 0 , sizeof ( tmp ) ) ;
snprintf ( tmp , sizeof ( tmp ) , " %s " , cmd ) ;
2022-06-29 10:37:36 +00:00
for ( i = 0 ; i < strlen ( tmp ) ; i + = ( size_t ) ret ) {
2013-11-24 15:00:12 +00:00
/* Write data in 8-byte chunks */
/* ret = usb->set_report(udev, 0, (unsigned char *)&tmp[i], 8); */
2022-06-29 10:37:36 +00:00
ret = usb_control_msg ( udev ,
USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE ,
0x09 , 0x200 , 0 ,
( usb_ctrl_charbuf ) & tmp [ i ] , 8 , 5000 ) ;
2013-11-24 15:00:12 +00:00
if ( ret < = 0 ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 3 , " send: %s (%d) " ,
ret ? nut_usb_strerror ( ret ) : " timeout " ,
ret ) ;
2013-11-24 15:00:12 +00:00
return ret ;
}
2015-04-30 13:53:36 +00:00
2013-11-24 15:00:12 +00:00
}
upsdebugx ( 3 , " send: %.*s " , ( int ) strcspn ( tmp , " \r " ) , tmp ) ;
/* Read reply */
memset ( buf , 0 , buflen ) ;
2022-06-29 10:37:36 +00:00
for ( i = 0 ; ( i < = buflen - 8 ) & & ( memchr ( buf , ' \r ' , buflen ) = = NULL ) ; i + = ( size_t ) ret ) {
2013-11-24 15:00:12 +00:00
/* Read data in 8-byte chunks */
/* ret = usb->get_interrupt(udev, (unsigned char *)&buf[i], 8, 1000); */
2022-06-29 10:37:36 +00:00
ret = usb_interrupt_read ( udev ,
0x81 ,
( usb_ctrl_charbuf ) & buf [ i ] , 8 , 1000 ) ;
2013-11-24 15:00:12 +00:00
2022-06-29 10:37:36 +00:00
/* Any errors here mean that we are unable to read a reply
* ( which will happen after successfully writing a command
* to the UPS ) */
2013-11-24 15:00:12 +00:00
if ( ret < = 0 ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 3 , " read: %s (%d) " ,
ret ? nut_usb_strerror ( ret ) : " timeout " ,
ret ) ;
2013-11-24 15:00:12 +00:00
return ret ;
}
2015-04-30 13:53:36 +00:00
snprintf ( tmp , sizeof ( tmp ) , " read [% 3d] " , ( int ) i ) ;
2022-06-29 10:37:36 +00:00
upsdebug_hex ( 5 , tmp , & buf [ i ] , ( size_t ) ret ) ;
2015-04-30 13:53:36 +00:00
2013-11-24 15:00:12 +00:00
}
upsdebugx ( 3 , " read: %.*s " , ( int ) strcspn ( buf , " \r " ) , buf ) ;
2022-06-29 10:37:36 +00:00
if ( i > INT_MAX ) {
upsdebugx ( 3 , " %s: read too much (%zu) " , __func__ , i ) ;
return - 1 ;
}
return ( int ) i ;
2013-11-24 15:00:12 +00:00
}
2016-07-18 00:11:41 +00:00
/* SGS communication subdriver */
static int sgs_command ( const char * cmd , char * buf , size_t buflen )
{
char tmp [ SMALLBUF ] ;
2022-06-29 10:37:36 +00:00
int ret = 0 ;
2016-07-18 00:11:41 +00:00
size_t cmdlen , i ;
2022-06-29 10:37:36 +00:00
if ( buflen > INT_MAX ) {
upsdebugx ( 3 , " %s: requested to read too much (%zu), "
" reducing buflen to (INT_MAX-1) " ,
__func__ , buflen ) ;
buflen = ( INT_MAX - 1 ) ;
}
2016-07-18 00:11:41 +00:00
/* Send command */
cmdlen = strlen ( cmd ) ;
2022-06-29 10:37:36 +00:00
for ( i = 0 ; i < cmdlen ; i + = ( size_t ) ret ) {
2016-07-18 00:11:41 +00:00
memset ( tmp , 0 , sizeof ( tmp ) ) ;
2022-06-29 10:37:36 +00:00
/* i and cmdlen are size_t nominally, but diff is not large */
ret = ( int ) ( ( cmdlen - i ) < 7 ? ( cmdlen - i ) : 7 ) ;
2016-07-18 00:11:41 +00:00
2022-06-29 10:37:36 +00:00
/* ret is between 0 and 7 */
tmp [ 0 ] = ( char ) ret ;
memcpy ( & tmp [ 1 ] , & cmd [ i ] , ( unsigned char ) ret ) ;
2016-07-18 00:11:41 +00:00
/* Write data in 8-byte chunks */
2022-06-29 10:37:36 +00:00
ret = usb_control_msg ( udev ,
USB_ENDPOINT_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE ,
0x09 , 0x200 , 0 ,
( usb_ctrl_charbuf ) tmp , 8 , 5000 ) ;
2016-07-18 00:11:41 +00:00
if ( ret < = 0 ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 3 , " send: %s (%d) " ,
ret ? nut_usb_strerror ( ret ) : " timeout " ,
ret ) ;
2016-07-18 00:11:41 +00:00
return ret ;
}
ret - - ;
}
upsdebugx ( 3 , " send: %.*s " , ( int ) strcspn ( cmd , " \r " ) , cmd ) ;
/* Read reply */
memset ( buf , 0 , buflen ) ;
2022-06-29 10:37:36 +00:00
for ( i = 0 ; i < = buflen - 8 ; i + = ( size_t ) ret ) {
2016-07-18 00:11:41 +00:00
memset ( tmp , 0 , sizeof ( tmp ) ) ;
/* Read data in 8-byte chunks */
2022-06-29 10:37:36 +00:00
ret = usb_interrupt_read ( udev ,
0x81 ,
( usb_ctrl_charbuf ) tmp , 8 , 1000 ) ;
2016-07-18 00:11:41 +00:00
/* No error!!! */
2022-06-29 10:37:36 +00:00
/* if (ret == -110) */
if ( ret = = ERROR_TIMEOUT )
2016-07-18 00:11:41 +00:00
break ;
2022-06-29 10:37:36 +00:00
/* Any errors here mean that we are unable to read a reply
* ( which will happen after successfully writing a command
* to the UPS ) */
2016-07-18 00:11:41 +00:00
if ( ret < = 0 ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 3 , " read: %s (%d) " ,
ret ? nut_usb_strerror ( ret ) : " timeout " ,
ret ) ;
2016-07-18 00:11:41 +00:00
return ret ;
}
/* Every call to read returns 8 bytes
* - > actually returned bytes : */
ret = tmp [ 0 ] < = 7 ? tmp [ 0 ] : 7 ;
if ( ret > 0 )
2022-06-29 10:37:36 +00:00
memcpy ( & buf [ i ] , & tmp [ 1 ] , ( unsigned char ) ret ) ;
2016-07-18 00:11:41 +00:00
snprintf ( tmp , sizeof ( tmp ) , " read [% 3d] " , ( int ) i ) ;
2022-06-29 10:37:36 +00:00
upsdebug_hex ( 5 , tmp , & buf [ i ] , ( size_t ) ret ) ;
}
2016-07-18 00:11:41 +00:00
2022-06-29 10:37:36 +00:00
/* If the reply lacks the expected terminating CR, add it (if there's enough space) */
if ( i & & memchr ( buf , ' \r ' , i ) = = NULL ) {
upsdebugx ( 4 , " %s: the reply lacks the expected terminating CR. " , __func__ ) ;
if ( i < buflen - 1 ) {
upsdebugx ( 4 , " %s: adding missing terminating CR. " , __func__ ) ;
buf [ i + + ] = ' \r ' ;
buf [ i ] = 0 ;
}
2016-07-18 00:11:41 +00:00
}
upsdebugx ( 3 , " read: %.*s " , ( int ) strcspn ( buf , " \r " ) , buf ) ;
2022-06-29 10:37:36 +00:00
if ( i > INT_MAX ) {
upsdebugx ( 3 , " %s: read too much (%zu) " , __func__ , i ) ;
return - 1 ;
}
return ( int ) i ;
2016-07-18 00:11:41 +00:00
}
2013-11-24 15:00:12 +00:00
/* Phoenix communication subdriver */
static int phoenix_command ( const char * cmd , char * buf , size_t buflen )
{
char tmp [ SMALLBUF ] ;
int ret ;
size_t i ;
2022-06-29 10:37:36 +00:00
if ( buflen > INT_MAX ) {
upsdebugx ( 3 , " %s: requested to read too much (%zu), "
" reducing buflen to (INT_MAX-1) " ,
__func__ , buflen ) ;
buflen = ( INT_MAX - 1 ) ;
}
2013-11-24 15:00:12 +00:00
for ( i = 0 ; i < 8 ; i + + ) {
/* Read data in 8-byte chunks */
/* ret = usb->get_interrupt(udev, (unsigned char *)tmp, 8, 1000); */
2022-06-29 10:37:36 +00:00
ret = usb_interrupt_read ( udev ,
0x81 ,
( usb_ctrl_charbuf ) tmp , 8 , 1000 ) ;
2013-11-24 15:00:12 +00:00
/* This USB to serial implementation is crappy.
2022-06-29 10:37:36 +00:00
* In order to read correct replies we need to flush the
* output buffers of the converter until we get no more
* data ( e . g . it times out ) . */
2013-11-24 15:00:12 +00:00
switch ( ret )
{
2022-06-29 10:37:36 +00:00
case ERROR_PIPE : /* Broken pipe */
2013-11-24 15:00:12 +00:00
usb_clear_halt ( udev , 0x81 ) ;
2022-06-29 10:37:36 +00:00
break ;
case ERROR_TIMEOUT : /* Connection timed out */
2013-11-24 15:00:12 +00:00
break ;
}
if ( ret < 0 ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 3 , " flush: %s (%d) " ,
nut_usb_strerror ( ret ) , ret ) ;
2013-11-24 15:00:12 +00:00
break ;
}
2022-06-29 10:37:36 +00:00
upsdebug_hex ( 4 , " dump " , tmp , ( size_t ) ret ) ;
2015-04-30 13:53:36 +00:00
2013-11-24 15:00:12 +00:00
}
/* Send command */
memset ( tmp , 0 , sizeof ( tmp ) ) ;
snprintf ( tmp , sizeof ( tmp ) , " %s " , cmd ) ;
2022-06-29 10:37:36 +00:00
for ( i = 0 ; i < strlen ( tmp ) ; i + = ( size_t ) ret ) {
2013-11-24 15:00:12 +00:00
/* Write data in 8-byte chunks */
/* ret = usb->set_report(udev, 0, (unsigned char *)&tmp[i], 8); */
2022-06-29 10:37:36 +00:00
ret = usb_control_msg ( udev ,
USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE ,
0x09 , 0x200 , 0 , ( usb_ctrl_charbuf ) & tmp [ i ] , 8 , 1000 ) ;
2013-11-24 15:00:12 +00:00
if ( ret < = 0 ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 3 , " send: %s (%d) " ,
ret ? nut_usb_strerror ( ret ) : " timeout " , ret ) ;
2013-11-24 15:00:12 +00:00
return ret ;
}
2015-04-30 13:53:36 +00:00
2013-11-24 15:00:12 +00:00
}
upsdebugx ( 3 , " send: %.*s " , ( int ) strcspn ( tmp , " \r " ) , tmp ) ;
/* Read reply */
memset ( buf , 0 , buflen ) ;
2022-06-29 10:37:36 +00:00
for ( i = 0 ; ( i < = buflen - 8 ) & & ( memchr ( buf , ' \r ' , buflen ) = = NULL ) ; i + = ( size_t ) ret ) {
2013-11-24 15:00:12 +00:00
/* Read data in 8-byte chunks */
/* ret = usb->get_interrupt(udev, (unsigned char *)&buf[i], 8, 1000); */
2022-06-29 10:37:36 +00:00
ret = usb_interrupt_read ( udev ,
0x81 ,
( usb_ctrl_charbuf ) & buf [ i ] , 8 , 1000 ) ;
2013-11-24 15:00:12 +00:00
2022-06-29 10:37:36 +00:00
/* Any errors here mean that we are unable to read a reply
* ( which will happen after successfully writing a command
* to the UPS ) */
2013-11-24 15:00:12 +00:00
if ( ret < = 0 ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 3 , " read: %s (%d) " ,
ret ? nut_usb_strerror ( ret ) : " timeout " , ret ) ;
2013-11-24 15:00:12 +00:00
return ret ;
}
2015-04-30 13:53:36 +00:00
snprintf ( tmp , sizeof ( tmp ) , " read [% 3d] " , ( int ) i ) ;
2022-06-29 10:37:36 +00:00
upsdebug_hex ( 5 , tmp , & buf [ i ] , ( size_t ) ret ) ;
2015-04-30 13:53:36 +00:00
2013-11-24 15:00:12 +00:00
}
upsdebugx ( 3 , " read: %.*s " , ( int ) strcspn ( buf , " \r " ) , buf ) ;
2022-06-29 10:37:36 +00:00
if ( i > INT_MAX ) {
upsdebugx ( 3 , " %s: read too much (%zu) " , __func__ , i ) ;
return - 1 ;
}
return ( int ) i ;
2013-11-24 15:00:12 +00:00
}
/* Ippon communication subdriver */
static int ippon_command ( const char * cmd , char * buf , size_t buflen )
{
char tmp [ 64 ] ;
2015-04-30 13:53:36 +00:00
int ret ;
size_t i , len ;
2013-11-24 15:00:12 +00:00
2022-06-29 10:37:36 +00:00
if ( buflen > INT_MAX ) {
upsdebugx ( 3 , " %s: requested to read too much (%zu), "
" reducing buflen to (INT_MAX-1) " ,
__func__ , buflen ) ;
buflen = ( INT_MAX - 1 ) ;
}
2013-11-24 15:00:12 +00:00
/* Send command */
snprintf ( tmp , sizeof ( tmp ) , " %s " , cmd ) ;
2022-06-29 10:37:36 +00:00
for ( i = 0 ; i < strlen ( tmp ) ; i + = ( size_t ) ret ) {
2013-11-24 15:00:12 +00:00
/* Write data in 8-byte chunks */
2022-06-29 10:37:36 +00:00
ret = usb_control_msg ( udev ,
USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE ,
0x09 , 0x2 , 0 , ( usb_ctrl_charbuf ) & tmp [ i ] , 8 , 1000 ) ;
2013-11-24 15:00:12 +00:00
if ( ret < = 0 ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 3 , " send: %s (%d) " ,
( ret ! = ERROR_TIMEOUT ) ? nut_usb_strerror ( ret ) : " Connection timed out " ,
ret ) ;
2013-11-24 15:00:12 +00:00
return ret ;
}
2015-04-30 13:53:36 +00:00
2013-11-24 15:00:12 +00:00
}
upsdebugx ( 3 , " send: %.*s " , ( int ) strcspn ( tmp , " \r " ) , tmp ) ;
/* Read all 64 bytes of the reply in one large chunk */
2022-06-29 10:37:36 +00:00
ret = usb_interrupt_read ( udev ,
0x81 ,
( usb_ctrl_charbuf ) tmp , sizeof ( tmp ) , 1000 ) ;
2013-11-24 15:00:12 +00:00
2022-06-29 10:37:36 +00:00
/* Any errors here mean that we are unable to read a reply
* ( which will happen after successfully writing a command
* to the UPS ) */
2013-11-24 15:00:12 +00:00
if ( ret < = 0 ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 3 , " read: %s (%d) " ,
( ret ! = ERROR_TIMEOUT ) ? nut_usb_strerror ( ret ) : " Connection timed out " ,
ret ) ;
2013-11-24 15:00:12 +00:00
return ret ;
}
2022-06-29 10:37:36 +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 . */
2015-04-30 13:53:36 +00:00
for ( i = 0 , len = 0 ; i < ( size_t ) ret ; i + + ) {
if ( tmp [ i ] ! = ' \r ' )
continue ;
len = + + i ;
break ;
2014-04-22 18:39:47 +00:00
}
2015-04-30 13:53:36 +00:00
/* Just in case there wasn't any '\r', fallback to string length, if any */
if ( ! len )
len = strlen ( tmp ) ;
2022-06-29 10:37:36 +00:00
upsdebug_hex ( 5 , " read " , tmp , ( size_t ) len ) ;
2015-04-30 13:53:36 +00:00
upsdebugx ( 3 , " read: %.*s " , ( int ) strcspn ( tmp , " \r " ) , tmp ) ;
len = len < buflen ? len : buflen - 1 ;
memset ( buf , 0 , buflen ) ;
memcpy ( buf , tmp , len ) ;
2022-06-29 10:37:36 +00:00
/* If the reply lacks the expected terminating CR, add it (if there's enough space) */
if ( len & & memchr ( buf , ' \r ' , len ) = = NULL ) {
upsdebugx ( 4 , " %s: the reply lacks the expected terminating CR. " , __func__ ) ;
if ( len < buflen - 1 ) {
upsdebugx ( 4 , " %s: adding missing terminating CR. " , __func__ ) ;
buf [ len + + ] = ' \r ' ;
buf [ len ] = 0 ;
}
}
if ( len > INT_MAX ) {
upsdebugx ( 3 , " %s: read too much (%zu) " , __func__ , len ) ;
return - 1 ;
}
2015-04-30 13:53:36 +00:00
return ( int ) len ;
2013-11-24 15:00:12 +00:00
}
2022-06-29 10:37:36 +00:00
static int hunnox_protocol ( int asking_for )
{
char buf [ 1030 ] ;
int langid_fix_local = 0x0409 ;
if ( langid_fix ! = - 1 ) {
langid_fix_local = langid_fix ;
}
switch ( hunnox_step ) {
case 0 :
upsdebugx ( 3 , " asking for: %02X " , 0x00 ) ;
usb_get_string ( udev , 0x00 ,
langid_fix_local , ( usb_ctrl_charbuf ) buf , 1026 ) ;
usb_get_string ( udev , 0x00 ,
langid_fix_local , ( usb_ctrl_charbuf ) buf , 1026 ) ;
usb_get_string ( udev , 0x01 ,
langid_fix_local , ( usb_ctrl_charbuf ) buf , 1026 ) ;
usleep ( 10000 ) ;
break ;
case 1 :
if ( asking_for ! = 0x0d ) {
upsdebugx ( 3 , " asking for: %02X " , 0x0d ) ;
usb_get_string ( udev , 0x0d ,
langid_fix_local , ( usb_ctrl_charbuf ) buf , 102 ) ;
}
break ;
case 2 :
if ( asking_for ! = 0x03 ) {
upsdebugx ( 3 , " asking for: %02X " , 0x03 ) ;
usb_get_string ( udev , 0x03 ,
langid_fix_local , ( usb_ctrl_charbuf ) buf , 102 ) ;
}
break ;
case 3 :
if ( asking_for ! = 0x0c ) {
upsdebugx ( 3 , " asking for: %02X " , 0x0c ) ;
usb_get_string ( udev , 0x0c ,
langid_fix_local , ( usb_ctrl_charbuf ) buf , 102 ) ;
}
break ;
default :
hunnox_step = 0 ;
}
hunnox_step + + ;
if ( hunnox_step > 3 ) {
hunnox_step = 1 ;
}
return 0 ;
}
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 ' } ,
2022-06-29 10:37:36 +00:00
{ NULL , 0 , ' \0 ' }
2013-11-24 15:00:12 +00:00
} ;
int i ;
upsdebugx ( 3 , " send: %.*s " , ( int ) strcspn ( cmd , " \r " ) , cmd ) ;
2022-06-29 10:37:36 +00:00
if ( buflen > INT_MAX ) {
upsdebugx ( 3 , " %s: requested to read too much (%zu), "
" reducing buflen to (INT_MAX-1) " ,
__func__ , buflen ) ;
buflen = ( INT_MAX - 1 ) ;
}
2013-11-24 15:00:12 +00:00
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 */
2022-06-29 10:37:36 +00:00
ret = usb_get_string ( udev ,
command [ i ] . index , langid_fix ,
( usb_ctrl_charbuf ) buf , buflen ) ;
2015-04-30 13:53:36 +00:00
} else {
2022-06-29 10:37:36 +00:00
ret = usb_get_string_simple ( udev ,
command [ i ] . index ,
( usb_ctrl_charbuf ) buf , buflen ) ;
2013-11-24 15:00:12 +00:00
}
if ( ret < = 0 ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 3 , " read: %s (%d) " ,
ret ? nut_usb_strerror ( ret ) : " timeout " , ret ) ;
2013-11-24 15:00:12 +00:00
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 ? */
2022-06-29 10:37:36 +00:00
unsigned int di , si , size = ( unsigned int ) buf [ 0 ] ;
2013-11-24 15:00:12 +00:00
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 ] ;
}
2022-06-29 10:37:36 +00:00
/* Note: effective range of di should be unsigned char */
2013-11-24 15:00:12 +00:00
buf [ di ] = 0 ;
2022-06-29 10:37:36 +00:00
ret = ( int ) di ;
}
/* If the reply lacks the expected terminating CR, add it (if there's enough space) */
if ( ret & & memchr ( buf , ' \r ' , ret ) = = NULL ) {
upsdebugx ( 4 , " %s: the reply lacks the expected terminating CR. " , __func__ ) ;
if ( ( size_t ) ret < buflen - 1 ) {
upsdebugx ( 4 , " %s: adding missing terminating CR. " , __func__ ) ;
buf [ ret + + ] = ' \r ' ;
buf [ ret ] = 0 ;
}
2013-11-24 15:00:12 +00:00
}
/* "UPS No Ack" has a special meaning */
2016-07-18 00:11:41 +00:00
if (
strcspn ( buf , " \r " ) = = 10 & &
! strncasecmp ( buf , " UPS No Ack " , 10 )
) {
2013-11-24 15:00:12 +00:00
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 ;
2022-06-29 10:37:36 +00:00
upsdebug_hex ( 5 , " read " , buf , ( size_t ) ret ) ;
2013-11-24 15:00:12 +00:00
upsdebugx ( 3 , " read: %.*s " , ( int ) strcspn ( buf , " \r " ) , buf ) ;
2015-04-30 13:53:36 +00:00
2013-11-24 15:00:12 +00:00
return ret ;
}
return 0 ;
}
/* Echo the unknown command back */
upsdebugx ( 3 , " read: %.*s " , ( int ) strcspn ( cmd , " \r " ) , cmd ) ;
return snprintf ( buf , buflen , " %s " , cmd ) ;
}
2015-04-30 13:53:36 +00:00
/* Fabula communication subdriver */
static int fabula_command ( const char * cmd , char * buf , size_t buflen )
{
const struct {
const char * str ; /* Megatec command */
const int index ; /* Fabula string index for this command */
} commands [ ] = {
{ " Q1 \r " , 0x03 , } , /* Status */
{ " F \r " , 0x0d , } , /* Ratings */
{ " I \r " , 0x0c , } , /* Vendor infos */
{ " Q \r " , 0x07 , } , /* Beeper toggle */
{ " C \r " , 0x0a , } , /* Cancel shutdown/Load on [0x(0..F)A]*/
2022-06-29 10:37:36 +00:00
{ NULL , 0 }
} ;
int i , ret , index = 0 ;
upsdebugx ( 3 , " send: %.*s " , ( int ) strcspn ( cmd , " \r " ) , cmd ) ;
if ( buflen > INT_MAX ) {
upsdebugx ( 3 , " %s: requested to read too much (%zu), "
" reducing buflen to (INT_MAX-1) " ,
__func__ , buflen ) ;
buflen = ( INT_MAX - 1 ) ;
}
for ( i = 0 ; commands [ i ] . str ; i + + ) {
if ( strcmp ( cmd , commands [ i ] . str ) )
continue ;
index = commands [ i ] . index ;
break ;
}
if ( ! index ) {
int val2 = - 1 ;
double val1 = - 1 ;
/* Shutdowns */
if (
sscanf ( cmd , " S%lfR%d \r " , & val1 , & val2 ) = = 2 | |
sscanf ( cmd , " S%lf \r " , & val1 ) = = 1
) {
double delay ;
/* 0x(1+)0 -> shutdown.stayoff (SnR0000)
* 0 x ( 1 + ) 8 - > shutdown . return ( Sn [ Rm ] , m ! = 0 )
* [ delay before restart is always 10 seconds ]
* + 0x10 ( 16 dec ) = next megatec delay
* ( min .5 = hex 0x1 * ; max 10 = hex 0xF * ) - > n < 1 ? - > n + = .1 ; n > = 1 ? - > n + = 1 */
/* delay: [.5..10] (-> seconds: [30..600]) */
delay = val1 < .5 ? .5 : val1 > 10 ? 10 : val1 ;
if ( delay < 1 )
index = 16 + round ( ( delay - .5 ) * 10 ) * 16 ;
else
index = 96 + ( delay - 1 ) * 16 ;
/* shutdown.return (Sn[Rm], m != 0) */
if ( val2 )
index + = 8 ;
/* Unknown commands */
} else {
/* Echo the unknown command back */
upsdebugx ( 3 , " read: %.*s " , ( int ) strcspn ( cmd , " \r " ) , cmd ) ;
return snprintf ( buf , buflen , " %s " , cmd ) ;
}
}
upsdebugx ( 4 , " command index: 0x%02x " , index ) ;
/* Send command/Read reply */
ret = usb_get_string_simple ( udev , index , ( usb_ctrl_charbuf ) buf , buflen ) ;
if ( ret < = 0 ) {
upsdebugx ( 3 , " read: %s (%d) " ,
ret ? nut_usb_strerror ( ret ) : " timeout " , ret ) ;
return ret ;
}
/* If the reply lacks the expected terminating CR, add it (if there's enough space) */
if ( memchr ( buf , ' \r ' , ret ) = = NULL ) {
upsdebugx ( 4 , " %s: the reply lacks the expected terminating CR. " , __func__ ) ;
if ( ( size_t ) ret < buflen - 1 ) {
upsdebugx ( 4 , " %s: adding missing terminating CR. " , __func__ ) ;
buf [ ret + + ] = ' \r ' ;
buf [ ret ] = 0 ;
}
}
upsdebug_hex ( 5 , " read " , buf , ( size_t ) ret ) ;
upsdebugx ( 3 , " read: %.*s " , ( int ) strcspn ( buf , " \r " ) , buf ) ;
/* The UPS always replies "UPS No Ack" when a supported command
* is issued ( either if it fails or if it succeeds ) . . */
if (
strcspn ( buf , " \r " ) = = 10 & &
! strncasecmp ( buf , " UPS No Ack " , 10 )
) {
/* ..because of that, always return 0 (with buf empty,
* as if it was a timeout ) : queries will see it as a failure ,
* instant commands ( ' megatec ' protocol ) as a success */
memset ( buf , 0 , buflen ) ;
return 0 ;
}
return ret ;
}
/* Hunnox communication subdriver, based on Fabula code above so repeats
* much of it currently . Possible future optimization is to refactor shared
* code into new routines to be called from both ( or more ) methods . */
static int hunnox_command ( const char * cmd , char * buf , size_t buflen )
{
/* The hunnox_patch was an argument in initial implementation of PR #638
* which added " hunnox " support ; keeping it fixed here helps to visibly
* track the modifications compared to original fabula_command ( ) e . g . to
* facilitate refactoring commented above , in the future .
*/
/* char hunnox_patch = 1; */
const struct {
const char * str ; /* Megatec command */
const int index ; /* Fabula string index for this command */
} commands [ ] = {
{ " Q1 \r " , 0x03 , } , /* Status */
{ " F \r " , 0x0d , } , /* Ratings */
{ " I \r " , 0x0c , } , /* Vendor infos */
{ " Q \r " , 0x07 , } , /* Beeper toggle */
{ " C \r " , 0x0a , } , /* Cancel shutdown/Load on [0x(0..F)A]*/
{ NULL , 0 }
2015-04-30 13:53:36 +00:00
} ;
int i , ret , index = 0 ;
upsdebugx ( 3 , " send: %.*s " , ( int ) strcspn ( cmd , " \r " ) , cmd ) ;
2022-06-29 10:37:36 +00:00
if ( buflen > INT_MAX ) {
upsdebugx ( 3 , " %s: requested to read too much (%zu), "
" reducing buflen to (INT_MAX-1) " ,
__func__ , buflen ) ;
buflen = ( INT_MAX - 1 ) ;
}
2015-04-30 13:53:36 +00:00
for ( i = 0 ; commands [ i ] . str ; i + + ) {
if ( strcmp ( cmd , commands [ i ] . str ) )
continue ;
index = commands [ i ] . index ;
break ;
}
if ( ! index ) {
int val2 = - 1 ;
double val1 = - 1 ;
/* Shutdowns */
if (
sscanf ( cmd , " S%lfR%d \r " , & val1 , & val2 ) = = 2 | |
sscanf ( cmd , " S%lf \r " , & val1 ) = = 1
) {
double delay ;
/* 0x(1+)0 -> shutdown.stayoff (SnR0000)
2022-06-29 10:37:36 +00:00
* 0 x ( 1 + ) 8 - > shutdown . return ( Sn [ Rm ] , m ! = 0 )
* [ delay before restart is always 10 seconds ]
* + 0x10 ( 16 dec ) = next megatec delay
* ( min .5 = hex 0x1 * ; max 10 = hex 0xF * ) - > n < 1 ? - > n + = .1 ; n > = 1 ? - > n + = 1 */
2015-04-30 13:53:36 +00:00
/* delay: [.5..10] (-> seconds: [30..600]) */
delay = val1 < .5 ? .5 : val1 > 10 ? 10 : val1 ;
if ( delay < 1 )
index = 16 + round ( ( delay - .5 ) * 10 ) * 16 ;
else
index = 96 + ( delay - 1 ) * 16 ;
/* shutdown.return (Sn[Rm], m != 0) */
if ( val2 )
index + = 8 ;
/* Unknown commands */
} else {
/* Echo the unknown command back */
upsdebugx ( 3 , " read: %.*s " , ( int ) strcspn ( cmd , " \r " ) , cmd ) ;
return snprintf ( buf , buflen , " %s " , cmd ) ;
}
}
upsdebugx ( 4 , " command index: 0x%02x " , index ) ;
2022-06-29 10:37:36 +00:00
/* if (hunnox_patch) { */
/* Enable lock-step protocol for Hunnox */
if ( hunnox_protocol ( index ) ! = 0 ) {
return 0 ;
}
/* Seems that if we inform a large buffer, the USB locks.
* This value was captured from the Windows " official " client .
* Note this should not be a problem programmatically : it just
* means that the caller reserved a longer buffer that we need
* in practice to write a response into .
*/
if ( buflen > 102 ) {
buflen = 102 ;
}
/* } */
2015-04-30 13:53:36 +00:00
/* Send command/Read reply */
2022-06-29 10:37:36 +00:00
if ( langid_fix ! = - 1 ) {
ret = usb_get_string ( udev ,
index , langid_fix , ( usb_ctrl_charbuf ) buf , buflen ) ;
} else {
ret = usb_get_string_simple ( udev ,
index , ( usb_ctrl_charbuf ) buf , buflen ) ;
}
2015-04-30 13:53:36 +00:00
if ( ret < = 0 ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 3 , " read: %s (%d) " ,
ret ? nut_usb_strerror ( ret ) : " timeout " ,
ret ) ;
2015-04-30 13:53:36 +00:00
return ret ;
}
2022-06-29 10:37:36 +00:00
/* if (hunnox_patch) { */
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 ] ) ;
return 0 ;
}
/* 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 = ( unsigned int ) 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 ] ;
}
/* Note: effective range of di should be unsigned char */
buf [ di ] = 0 ;
ret = ( int ) di ;
}
/* } */
upsdebug_hex ( 5 , " read " , buf , ( size_t ) ret ) ;
2015-04-30 13:53:36 +00:00
upsdebugx ( 3 , " read: %.*s " , ( int ) strcspn ( buf , " \r " ) , buf ) ;
2022-06-29 10:37:36 +00:00
/* The UPS always replies "UPS No Ack" when a supported command
* is issued ( either if it fails or if it succeeds ) . . */
2016-07-18 00:11:41 +00:00
if (
strcspn ( buf , " \r " ) = = 10 & &
! strncasecmp ( buf , " UPS No Ack " , 10 )
) {
2022-06-29 10:37:36 +00:00
/* ..because of that, always return 0 (with buf empty,
* as if it was a timeout ) : queries will see it as a failure ,
* instant commands ( ' megatec ' protocol ) as a success */
2016-07-18 00:11:41 +00:00
memset ( buf , 0 , buflen ) ;
2015-04-30 13:53:36 +00:00
return 0 ;
2016-07-18 00:11:41 +00:00
}
2015-04-30 13:53:36 +00:00
return ret ;
}
/* Fuji communication subdriver */
static int fuji_command ( const char * cmd , char * buf , size_t buflen )
{
unsigned char tmp [ 8 ] ;
char command [ SMALLBUF ] = " " ,
read [ SMALLBUF ] = " " ;
2022-06-29 10:37:36 +00:00
int ret , val2 ;
unsigned char answer_len ;
2015-04-30 13:53:36 +00:00
double val1 ;
size_t i ;
const struct {
const char * command ; /* Megatec command */
2022-06-29 10:37:36 +00:00
const unsigned char answer_len ; /* Expected length of the answer
* to the ongoing query */
2015-04-30 13:53:36 +00:00
} query [ ] = {
{ " Q1 " , 47 } ,
{ " F " , 22 } ,
{ " I " , 39 } ,
2022-06-29 10:37:36 +00:00
{ NULL , 0 }
2015-04-30 13:53:36 +00:00
} ;
2022-06-29 10:37:36 +00:00
if ( buflen > INT_MAX ) {
upsdebugx ( 3 , " %s: requested to read too much (%zu), "
" reducing buflen to (INT_MAX-1) " ,
__func__ , buflen ) ;
buflen = ( INT_MAX - 1 ) ;
}
2015-04-30 13:53:36 +00:00
/*
2022-06-29 10:37:36 +00:00
* Queries ( b1 . . b8 ) sent ( as a 8 - bytes interrupt ) to the UPS
* adopt the following scheme :
2015-04-30 13:53:36 +00:00
*
* b1 : 0x80
* b2 : 0x06
* b3 : < LEN >
* b4 : 0x03
* b5 . . bn : < COMMAND >
* bn + 1. . b7 : [ < PADDING > ]
* b8 : < ANSWER_LEN >
*
* Where :
* < LEN > Length ( in Hex ) of the command ( without the trailing CR ) + 1
* < COMMAND > Command / query ( without the trailing CR )
* [ < PADDING > ] 0x00 padding to the 7 th byte
2022-06-29 10:37:36 +00:00
* < ANSWER_LEN > Expected length ( in Hex ) of the answer to the ongoing
* query ( 0 when no reply is expected , i . e . commands )
2015-04-30 13:53:36 +00:00
*
2022-06-29 10:37:36 +00:00
* Replies to queries ( commands are followed by action without
* any reply ) are sent from the UPS ( in 8 - byte chunks ) with
* 0x00 padding after the trailing CR to full 8 bytes .
2015-04-30 13:53:36 +00:00
*
*/
/* Send command */
/* Remove the CR */
snprintf ( command , sizeof ( command ) , " %.*s " , ( int ) strcspn ( cmd , " \r " ) , cmd ) ;
2022-06-29 10:37:36 +00:00
/* Length of the command that will be sent to the UPS can be
* at most : 8 - 5 ( 0x80 , 0x06 , < LEN > , 0x03 , < ANSWER_LEN > ) = 3.
* As a consequence also ' SnRm ' commands ( shutdown . { return , stayoff }
* and load . off ) are not supported .
* So , map all the ' SnRm ' shutdown . returns ( m ! = 0 ) as the
* corresponding ' Sn ' commands , meanwhile ignoring ups . delay . start
* and making the UPS turn on the load as soon as power is back . */
2015-04-30 13:53:36 +00:00
if ( sscanf ( cmd , " S%lfR%d \r " , & val1 , & val2 ) = = 2 & & val2 ) {
upsdebugx ( 4 , " %s: trimming '%s' to '%.*s' " , __func__ , command , 3 , command ) ;
command [ 3 ] = 0 ;
}
/* Too long command */
if ( strlen ( command ) > 3 ) {
/* Be 'megatec-y': echo the unsupported command back */
upsdebugx ( 3 , " %s: unsupported command %s " , __func__ , command ) ;
return snprintf ( buf , buflen , " %s " , cmd ) ;
}
2022-06-29 10:37:36 +00:00
/* Expected length of the answer to the ongoing query
* ( 0 when no reply is expected , i . e . commands ) */
2015-04-30 13:53:36 +00:00
answer_len = 0 ;
for ( i = 0 ; query [ i ] . command ; i + + ) {
if ( strcmp ( command , query [ i ] . command ) )
continue ;
answer_len = query [ i ] . answer_len ;
break ;
}
memset ( tmp , 0 , sizeof ( tmp ) ) ;
/* 0x80 */
tmp [ 0 ] = 0x80 ;
/* 0x06 */
tmp [ 1 ] = 0x06 ;
2022-06-29 10:37:36 +00:00
/* <LEN>; per above under 3 */
tmp [ 2 ] = ( unsigned char ) strlen ( command ) + 1 ;
2015-04-30 13:53:36 +00:00
/* 0x03 */
tmp [ 3 ] = 0x03 ;
/* <COMMAND> */
memcpy ( & tmp [ 4 ] , command , strlen ( command ) ) ;
/* <ANSWER_LEN> */
tmp [ 7 ] = answer_len ;
upsdebug_hex ( 4 , " command " , ( char * ) tmp , 8 ) ;
/* Write data */
2022-06-29 10:37:36 +00:00
ret = usb_interrupt_write ( udev ,
USB_ENDPOINT_OUT | 2 ,
( const usb_ctrl_charbuf ) tmp ,
8 , USB_TIMEOUT ) ;
2015-04-30 13:53:36 +00:00
if ( ret < = 0 ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 3 , " send: %s (%d) " ,
ret ? nut_usb_strerror ( ret ) : " timeout " , ret ) ;
2015-04-30 13:53:36 +00:00
return ret ;
}
upsdebugx ( 3 , " send: %s " , command ) ;
/* Read reply */
memset ( buf , 0 , buflen ) ;
2022-06-29 10:37:36 +00:00
for ( i = 0 ; ( i < = buflen - 8 ) & & ( memchr ( buf , ' \r ' , buflen ) = = NULL ) ; i + = ( size_t ) ret ) {
2015-04-30 13:53:36 +00:00
/* Read data in 8-byte chunks */
2022-06-29 10:37:36 +00:00
ret = usb_interrupt_read ( udev ,
USB_ENDPOINT_IN | 1 ,
( usb_ctrl_charbuf ) & buf [ i ] , 8 , 1000 ) ;
2015-04-30 13:53:36 +00:00
2022-06-29 10:37:36 +00:00
/* Any errors here mean that we are unable to read a reply
* ( which will happen after successfully writing a command
* to the UPS ) */
2015-04-30 13:53:36 +00:00
if ( ret < = 0 ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 3 , " read: %s (%d) " ,
ret ? nut_usb_strerror ( ret ) : " timeout " , ret ) ;
2015-04-30 13:53:36 +00:00
return ret ;
}
snprintf ( read , sizeof ( read ) , " read [%3d] " , ( int ) i ) ;
2022-06-29 10:37:36 +00:00
upsdebug_hex ( 5 , read , & buf [ i ] , ( size_t ) ret ) ;
2015-04-30 13:53:36 +00:00
}
upsdebugx ( 3 , " read: %.*s " , ( int ) strcspn ( buf , " \r " ) , buf ) ;
/* As Fuji units return the reply in 8-byte chunks always padded to the 8th byte with 0x00, we need to calculate and return the length of the actual response here. */
return ( int ) strlen ( buf ) ;
}
2022-06-29 10:37:36 +00:00
/* Phoenixtec (Masterguard) communication subdriver */
static int phoenixtec_command ( const char * cmd , char * buf , size_t buflen )
{
int ret ;
char * p , * e = NULL ;
char * l [ ] = { " T " , " TL " , " S " , " C " , " CT " , " M " , " N " , " O " , " SRC " , " FCLR " , " SS " , " TUD " , " SSN " , NULL } ; /* commands that don't return an answer */
char * * lp ;
size_t cmdlen = strlen ( cmd ) ;
if ( cmdlen > INT_MAX ) {
upsdebugx ( 3 , " %s: requested command is too long (%zu) " ,
__func__ , cmdlen ) ;
return 0 ;
}
if ( buflen > INT_MAX ) {
upsdebugx ( 3 , " %s: requested to read too much (%zu), "
" reducing buflen to (INT_MAX-1) " ,
__func__ , buflen ) ;
buflen = ( INT_MAX - 1 ) ;
}
if ( ( ret = usb_control_msg ( udev ,
USB_ENDPOINT_OUT | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT ,
0x0d , 0 , 0 , ( usb_ctrl_charbuf ) cmd , ( int ) cmdlen , 1000 ) ) < = 0
) {
upsdebugx ( 3 , " send: %s (%d) " ,
ret ? nut_usb_strerror ( ret ) : " timeout " ,
ret ) ;
* buf = ' \0 ' ;
return ret ;
}
for ( lp = l ; * lp ! = NULL ; lp + + ) {
const char * q ;
int b ;
p = * lp ; q = cmd ; b = 1 ;
while ( * p ! = ' \0 ' ) {
if ( * p + + ! = * q + + ) {
b = 0 ;
break ;
}
}
if ( b & & * q > = ' A ' & & * q < = ' Z ' ) b = 0 ; /* "M" not to match "MSO" */
if ( b ) {
upsdebugx ( 4 , " command %s returns no answer " , * lp ) ;
* buf = ' \0 ' ;
return 0 ;
}
}
for ( p = buf ; p < buf + buflen ; p + = ret ) {
/* buflen constrained to INT_MAX above, so we can cast: */
if ( ( ret = usb_interrupt_read ( udev ,
USB_ENDPOINT_IN | 1 ,
( usb_ctrl_charbuf ) p , ( int ) ( buf + buflen - p ) , 1000 ) ) < = 0
) {
upsdebugx ( 3 , " read: %s (%d) " ,
ret ? nut_usb_strerror ( ret ) : " timeout " ,
ret ) ;
* buf = ' \0 ' ;
return ret ;
}
if ( ( e = memchr ( p , ' \r ' , ( size_t ) ret ) ) ! = NULL ) break ;
}
if ( e ! = NULL & & + + e < buf + buflen ) {
* e = ' \0 ' ;
/* buflen constrained to INT_MAX above, so we can cast: */
return ( int ) ( e - buf ) ;
} else {
upsdebugx ( 3 , " read: buflen %zu too small " , buflen ) ;
* buf = ' \0 ' ;
return 0 ;
}
}
/* SNR communication subdriver */
static int snr_command ( const char * cmd , char * buf , size_t buflen )
{
/*ATTENTION: This subdriver uses short buffer with length 102 byte*/
const struct {
const char * str ; /* Megatec command */
const int index ; /* 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 , ' # ' } ,
{ NULL , 0 , ' \0 ' }
} ;
int i ;
upsdebugx ( 3 , " send: %.*s " , ( int ) strcspn ( cmd , " \r " ) , cmd ) ;
if ( buflen > INT_MAX ) {
upsdebugx ( 3 , " %s: requested to read too much (%zu), "
" reducing buflen to (INT_MAX-1) " ,
__func__ , buflen ) ;
buflen = ( INT_MAX - 1 ) ;
}
if ( buflen < 102 ) {
upsdebugx ( 4 , " size of buf less than 102 byte! " ) ;
return 0 ;
}
/* Prepare SNR-UPS for communication.
* Without the interrupt UPS returns zeros for some time ,
* and afterwards NUT returns a communications error .
*/
usb_interrupt_read ( udev ,
0x81 ,
( usb_ctrl_charbuf ) buf , 102 , 1000 ) ;
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 ( udev ,
command [ i ] . index , langid_fix ,
( usb_ctrl_charbuf ) buf , 102 ) ;
if ( ret < = 0 ) {
upsdebugx ( 3 , " read: %s (%d) " ,
ret ? nut_usb_strerror ( ret ) : " timeout " ,
ret ) ;
return ret ;
}
/* This may serve in the future */
upsdebugx ( 1 , " received %d (%d) " , ret , buf [ 0 ] ) ;
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 = ( unsigned int ) 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 ] ;
}
/* Note: effective range of di should be unsigned char */
buf [ di ] = 0 ;
ret = ( int ) di ;
/* "UPS No Ack" has a special meaning */
if (
strcspn ( buf , " \r " ) = = 10 & &
! strncasecmp ( buf , " UPS No Ack " , 10 )
) {
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 ;
upsdebug_hex ( 5 , " read " , buf , ( size_t ) ret ) ;
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 int ablerex_command ( const char * cmd , char * buf , size_t buflen )
{
int iii ;
int len ;
int idx ;
char tmp [ 64 ] ;
char tmpryy [ 64 ] ;
upsdebugx ( 3 , " send: %.*s " , ( int ) strcspn ( cmd , " \r " ) , cmd ) ;
if ( buflen > INT_MAX ) {
upsdebugx ( 3 , " %s: requested to read too much (%zu), reducing buflen to (INT_MAX-1) " ,
__func__ , buflen ) ;
buflen = ( INT_MAX - 1 ) ;
}
int retry ;
for ( retry = 0 ; retry < 3 ; retry + + ) {
int ret ;
memset ( buf , 0 , buflen ) ;
tmp [ 0 ] = 0x05 ;
tmp [ 1 ] = 0 ;
tmp [ 2 ] = 1 + ( char ) strcspn ( cmd , " \r " ) ;
for ( iii = 0 ; iii < tmp [ 2 ] ; iii + + )
{
tmp [ 3 + iii ] = cmd [ iii ] ;
}
ret = usb_control_msg ( udev ,
0x21 ,
0x09 , 0x305 , 0 ,
( usb_ctrl_charbuf ) tmp , 47 , 1000 ) ;
upsdebugx ( 3 , " R11 read: %s " , ret ? nut_usb_strerror ( ret ) : " timeout " ) ;
usleep ( 500000 ) ;
tmpryy [ 0 ] = 0x05 ;
ret = usb_control_msg ( udev ,
0xA1 ,
0x01 , 0x305 , 0 ,
( usb_ctrl_charbuf ) tmpryy , 47 , 1000 ) ;
upsdebugx ( 3 , " R2 read%d: %.*s " , ret , ret , tmpryy ) ;
len = 0 ;
for ( idx = 0 ; idx < 47 ; idx + + )
{
buf [ idx ] = tmpryy [ idx ] ;
if ( tmpryy [ idx ] = = ' \r ' )
{
len = idx ;
break ;
}
}
upsdebugx ( 3 , " R3 read%d: %.*s " , len , len , tmpryy ) ;
if ( len > 0 ) {
len + + ;
}
if ( ret < = 0 ) {
upsdebugx ( 3 , " read: %s " , ret ? nut_usb_strerror ( ret ) : " timeout " ) ;
return ret ;
}
upsdebugx ( 1 , " received %d (%d) " , ret , buf [ 0 ] ) ;
if ( ( ! strcasecmp ( cmd , " Q1 \r " ) ) & & len ! = 47 ) continue ;
if ( ( ! strcasecmp ( cmd , " I \r " ) ) & & len ! = 39 ) continue ;
if ( ( ! strcasecmp ( cmd , " F \r " ) ) & & len ! = 22 ) continue ;
if ( ( ! strcasecmp ( cmd , " Q5 \r " ) ) & & len ! = 22 )
{
buf [ 0 ] = ' ( ' ;
for ( idx = 1 ; idx < 47 ; idx + + )
{
buf [ idx ] = 0 ;
}
upsdebugx ( 3 , " read Q5 Fail... " ) ;
return 22 ;
}
upsdebugx ( 3 , " read: %.*s " , ( int ) strcspn ( buf , " \r " ) , buf ) ;
return len ;
}
return 0 ;
}
static void * ablerex_subdriver_fun ( USBDevice_t * device )
{
NUT_UNUSED_VARIABLE ( device ) ;
subdriver_command = & ablerex_command ;
return NULL ;
}
/* Armac communication subdriver
*
* This reproduces a communication protocol used by an old PowerManagerII
* software , which doesn ' t seem to be Armac specific . The banner is : " 2004
* Richcomm Technologies , Inc . Dec 27 2005 ver 1.1 . " Maybe other Richcomm UPSes
* would work with this - better than with the richcomm_usb driver .
*/
static int armac_command ( const char * cmd , char * buf , size_t buflen )
{
char tmpbuf [ 6 ] ;
int ret = 0 ;
size_t i , bufpos ;
const size_t cmdlen = strlen ( cmd ) ;
/* UPS ignores (doesn't echo back) unsupported commands which makes
* the initialization long . List commands tested to be unsupported :
*/
const char * unsupported [ ] = {
" QGS \r " ,
" QS \r " ,
" QPI \r " ,
" M \r " ,
" D \r " ,
NULL
} ;
for ( i = 0 ; unsupported [ i ] ! = NULL ; i + + ) {
if ( strcmp ( cmd , unsupported [ i ] ) = = 0 ) {
upsdebugx ( 2 ,
" armac: unsupported cmd: %.*s " ,
( int ) strcspn ( cmd , " \r " ) , cmd ) ;
return snprintf ( buf , buflen , " %s " , cmd ) ;
}
}
upsdebugx ( 4 , " armac command %.*s " , ( int ) strcspn ( cmd , " \r " ) , cmd ) ;
/* Send command to the UPS in 3-byte chunks. Most fit 1 chunk, except for eg.
* parameterized tests . */
for ( i = 0 ; i < cmdlen ; ) {
const size_t bytes_to_send = ( cmdlen < = ( i + 3 ) ) ? ( cmdlen - i ) : 3 ;
memset ( tmpbuf , 0 , sizeof ( tmpbuf ) ) ;
tmpbuf [ 0 ] = 0xa0 + bytes_to_send ;
memcpy ( tmpbuf + 1 , cmd + i , bytes_to_send ) ;
ret = usb_control_msg ( udev ,
USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE ,
0x09 , 0x200 , 0 ,
( usb_ctrl_charbuf ) tmpbuf , 4 , 5000 ) ;
i + = bytes_to_send ;
}
if ( ret < = 0 ) {
upsdebugx ( 1 ,
" send control: %s (%d) " ,
ret ? nut_usb_strerror ( ret ) : " timeout " ,
ret ) ;
return ret ;
}
memset ( buf , 0 , buflen ) ;
bufpos = 0 ;
while ( bufpos + 6 < buflen ) {
size_t bytes_available ;
/* Read data in 6-byte chunks */
ret = usb_interrupt_read ( udev ,
0x81 ,
( usb_ctrl_charbuf ) tmpbuf , 6 , 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 ! = 6 ) {
upsdebugx ( 1 ,
" interrupt read error: %s (%d) " ,
ret ? nut_usb_strerror ( ret ) : " timeout " ,
ret ) ;
return ret ;
}
upsdebugx ( 4 ,
" read: ret %d buf %02hhx: %02hhx %02hhx %02hhx %02hhx %02hhx >%c%c%c%c%c< " ,
ret ,
tmpbuf [ 0 ] , tmpbuf [ 1 ] , tmpbuf [ 2 ] , tmpbuf [ 3 ] , tmpbuf [ 4 ] , tmpbuf [ 5 ] ,
tmpbuf [ 1 ] , tmpbuf [ 2 ] , tmpbuf [ 3 ] , tmpbuf [ 4 ] , tmpbuf [ 5 ] ) ;
bytes_available = ( unsigned char ) tmpbuf [ 0 ] & 0x0f ;
if ( bytes_available = = 0 ) {
/* End of transfer */
break ;
}
memcpy ( buf + bufpos , tmpbuf + 1 , bytes_available ) ;
bufpos + = bytes_available ;
if ( bytes_available < = 2 ) {
/* Slow down, let the UPS buffer more bytes */
usleep ( 15000 ) ;
}
}
if ( bufpos + 6 > = buflen ) {
upsdebugx ( 2 , " Protocol error, too much data read. " ) ;
return - 1 ;
}
upsdebugx ( 3 , " armac command %.*s response read: '%.*s' " ,
( int ) strcspn ( cmd , " \r " ) , cmd ,
( int ) strcspn ( buf , " \r " ) , buf
) ;
return ( int ) bufpos ;
}
2013-11-24 15:00:12 +00:00
static void * cypress_subdriver ( USBDevice_t * device )
{
2022-06-29 10:37:36 +00:00
NUT_UNUSED_VARIABLE ( device ) ;
2013-11-24 15:00:12 +00:00
subdriver_command = & cypress_command ;
return NULL ;
}
2016-07-18 00:11:41 +00:00
static void * sgs_subdriver ( USBDevice_t * device )
{
2022-06-29 10:37:36 +00:00
NUT_UNUSED_VARIABLE ( device ) ;
2016-07-18 00:11:41 +00:00
subdriver_command = & sgs_command ;
return NULL ;
}
2013-11-24 15:00:12 +00:00
static void * ippon_subdriver ( USBDevice_t * device )
{
2022-06-29 10:37:36 +00:00
NUT_UNUSED_VARIABLE ( device ) ;
2013-11-24 15:00:12 +00:00
subdriver_command = & ippon_command ;
return NULL ;
}
static void * krauler_subdriver ( USBDevice_t * device )
{
2022-06-29 10:37:36 +00:00
NUT_UNUSED_VARIABLE ( device ) ;
2013-11-24 15:00:12 +00:00
subdriver_command = & krauler_command ;
return NULL ;
}
static void * phoenix_subdriver ( USBDevice_t * device )
{
2022-06-29 10:37:36 +00:00
NUT_UNUSED_VARIABLE ( device ) ;
2013-11-24 15:00:12 +00:00
subdriver_command = & phoenix_command ;
return NULL ;
}
2015-04-30 13:53:36 +00:00
static void * fabula_subdriver ( USBDevice_t * device )
{
2022-06-29 10:37:36 +00:00
NUT_UNUSED_VARIABLE ( device ) ;
2015-04-30 13:53:36 +00:00
subdriver_command = & fabula_command ;
return NULL ;
}
2022-06-29 10:37:36 +00:00
static void * phoenixtec_subdriver ( USBDevice_t * device )
{
NUT_UNUSED_VARIABLE ( device ) ;
subdriver_command = & phoenixtec_command ;
return NULL ;
}
/* Note: the "hunnox_subdriver" name is taken by the subdriver_t structure */
static void * fabula_hunnox_subdriver ( USBDevice_t * device )
{
NUT_UNUSED_VARIABLE ( device ) ;
subdriver_command = & hunnox_command ;
return NULL ;
}
2015-04-30 13:53:36 +00:00
static void * fuji_subdriver ( USBDevice_t * device )
{
2022-06-29 10:37:36 +00:00
NUT_UNUSED_VARIABLE ( device ) ;
2015-04-30 13:53:36 +00:00
subdriver_command = & fuji_command ;
return NULL ;
}
2022-06-29 10:37:36 +00:00
static void * snr_subdriver ( USBDevice_t * device )
{
NUT_UNUSED_VARIABLE ( device ) ;
subdriver_command = & snr_command ;
return NULL ;
}
static void * armac_subdriver ( USBDevice_t * device )
{
NUT_UNUSED_VARIABLE ( device ) ;
subdriver_command = & armac_command ;
return NULL ;
}
2015-04-30 13:53:36 +00:00
/* USB device match structure */
typedef struct {
const int vendorID ; /* USB device's VendorID */
const int productID ; /* USB device's ProductID */
const char * vendor ; /* USB device's iManufacturer string */
const char * product ; /* USB device's iProduct string */
void * ( * fun ) ( USBDevice_t * ) ; /* Handler for specific processing */
} qx_usb_device_id_t ;
/* USB VendorID/ProductID/iManufacturer/iProduct match - note: rightmost comment is used for naming rules by tools/nut-usbinfo.pl */
static qx_usb_device_id_t qx_usb_id [ ] = {
{ USB_DEVICE ( 0x05b8 , 0x0000 ) , NULL , NULL , & cypress_subdriver } , /* Agiler UPS */
2022-06-29 10:37:36 +00:00
{ USB_DEVICE ( 0xffff , 0x0000 ) , NULL , NULL , & ablerex_subdriver_fun } , /* Ablerex 625L USB (Note: earlier best-fit was "krauler_subdriver" before PR #1135) */
{ USB_DEVICE ( 0x1cb0 , 0x0035 ) , NULL , NULL , & krauler_subdriver } , /* Legrand Daker DK / DK Plus */
2015-04-30 13:53:36 +00:00
{ USB_DEVICE ( 0x0665 , 0x5161 ) , NULL , NULL , & cypress_subdriver } , /* Belkin F6C1200-UNV/Voltronic Power UPSes */
2022-06-29 10:37:36 +00:00
{ USB_DEVICE ( 0x06da , 0x0002 ) , " Phoenixtec Power " , " USB Cable (V2.00) " , & phoenixtec_subdriver } , /* Masterguard A Series */
2015-04-30 13:53:36 +00:00
{ USB_DEVICE ( 0x06da , 0x0002 ) , NULL , NULL , & cypress_subdriver } , /* Online Yunto YQ450 */
{ USB_DEVICE ( 0x06da , 0x0003 ) , NULL , NULL , & ippon_subdriver } , /* Mustek Powermust */
{ USB_DEVICE ( 0x06da , 0x0004 ) , NULL , NULL , & cypress_subdriver } , /* Phoenixtec Innova 3/1 T */
{ USB_DEVICE ( 0x06da , 0x0005 ) , NULL , NULL , & cypress_subdriver } , /* Phoenixtec Innova RT */
{ USB_DEVICE ( 0x06da , 0x0201 ) , NULL , NULL , & cypress_subdriver } , /* Phoenixtec Innova T */
{ USB_DEVICE ( 0x06da , 0x0601 ) , NULL , NULL , & phoenix_subdriver } , /* Online Zinto A */
{ USB_DEVICE ( 0x0f03 , 0x0001 ) , NULL , NULL , & cypress_subdriver } , /* Unitek Alpha 1200Sx */
{ USB_DEVICE ( 0x14f0 , 0x00c9 ) , NULL , NULL , & phoenix_subdriver } , /* GE EP series */
2022-06-29 10:37:36 +00:00
{ USB_DEVICE ( 0x0483 , 0x0035 ) , NULL , NULL , & sgs_subdriver } , /* TS Shara UPSes; vendor ID 0x0483 is from ST Microelectronics - with product IDs delegated to different OEMs */
2015-04-30 13:53:36 +00:00
{ USB_DEVICE ( 0x0001 , 0x0000 ) , " MEC " , " MEC0003 " , & fabula_subdriver } , /* Fideltronik/MEC LUPUS 500 USB */
2022-06-29 10:37:36 +00:00
{ USB_DEVICE ( 0x0001 , 0x0000 ) , NULL , " MEC0003 " , & fabula_hunnox_subdriver } , /* Hunnox HNX 850, reported to also help support Powercool and some other devices; closely related to fabula with tweaks */
2015-04-30 13:53:36 +00:00
{ USB_DEVICE ( 0x0001 , 0x0000 ) , " ATCL FOR UPS " , " ATCL FOR UPS " , & fuji_subdriver } , /* Fuji UPSes */
{ USB_DEVICE ( 0x0001 , 0x0000 ) , NULL , NULL , & krauler_subdriver } , /* Krauler UP-M500VA */
2022-06-29 10:37:36 +00:00
{ USB_DEVICE ( 0x0001 , 0x0000 ) , NULL , " MEC0003 " , & snr_subdriver } , /* SNR-UPS-LID-XXXX UPSes */
{ USB_DEVICE ( 0x0925 , 0x1234 ) , NULL , NULL , & armac_subdriver } , /* Armac UPS and maybe other richcomm-like or using old PowerManagerII software */
2013-11-24 15:00:12 +00:00
/* End of list */
2015-04-30 13:53:36 +00:00
{ - 1 , - 1 , NULL , NULL , NULL }
2013-11-24 15:00:12 +00:00
} ;
2015-04-30 13:53:36 +00:00
static int qx_is_usb_device_supported ( qx_usb_device_id_t * usb_device_id_list , USBDevice_t * device )
{
int retval = NOT_SUPPORTED ;
qx_usb_device_id_t * usbdev ;
for ( usbdev = usb_device_id_list ; usbdev - > vendorID ! = - 1 ; usbdev + + ) {
if ( usbdev - > vendorID ! = device - > VendorID )
continue ;
/* Flag as possibly supported if we see a known vendor */
retval = POSSIBLY_SUPPORTED ;
if ( usbdev - > productID ! = device - > ProductID )
continue ;
2022-06-29 10:37:36 +00:00
if ( usbdev - > vendor
& & ( ! device - > Vendor | | strcasecmp ( usbdev - > vendor , device - > Vendor ) )
) {
2015-04-30 13:53:36 +00:00
continue ;
2022-06-29 10:37:36 +00:00
}
2015-04-30 13:53:36 +00:00
2022-06-29 10:37:36 +00:00
if ( usbdev - > product
& & ( ! device - > Product | | strcasecmp ( usbdev - > product , device - > Product ) )
) {
2015-04-30 13:53:36 +00:00
continue ;
2022-06-29 10:37:36 +00:00
}
2015-04-30 13:53:36 +00:00
/* Call the specific handler, if it exists */
if ( usbdev - > fun ! = NULL )
( * usbdev - > fun ) ( device ) ;
return SUPPORTED ;
}
return retval ;
}
2013-11-24 15:00:12 +00:00
static int device_match_func ( USBDevice_t * hd , void * privdata )
{
2022-06-29 10:37:36 +00:00
NUT_UNUSED_VARIABLE ( privdata ) ;
2013-11-24 15:00:12 +00:00
if ( subdriver_command ) {
return 1 ;
}
2015-04-30 13:53:36 +00:00
switch ( qx_is_usb_device_supported ( qx_usb_id , hd ) )
2013-11-24 15:00:12 +00:00
{
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 == */
2016-07-18 00:11:41 +00:00
/* See header file for details. */
2013-11-24 15:00:12 +00:00
int instcmd ( const char * cmdname , const char * extradata )
{
item_t * item ;
char value [ SMALLBUF ] ;
if ( ! strcasecmp ( cmdname , " beeper.off " ) ) {
/* Compatibility mode for old command */
2022-06-29 10:37:36 +00:00
upslogx ( LOG_WARNING ,
" The 'beeper.off' command has been renamed to 'beeper.disable' " ) ;
2013-11-24 15:00:12 +00:00
return instcmd ( " beeper.disable " , NULL ) ;
}
if ( ! strcasecmp ( cmdname , " beeper.on " ) ) {
/* Compatibility mode for old command */
2022-06-29 10:37:36 +00:00
upslogx ( LOG_WARNING ,
" The 'beeper.on' command has been renamed to 'beeper.enable' " ) ;
2013-11-24 15:00:12 +00:00
return instcmd ( " beeper.enable " , NULL ) ;
}
2022-06-29 10:37:36 +00:00
upslogx ( LOG_INFO , " %s(%s, %s) " ,
__func__ , cmdname ,
extradata ? extradata : " [NULL] " ) ;
2013-11-24 15:00:12 +00:00
/* 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 ;
}
2022-06-29 10:37:36 +00:00
return instcmd ( " load.off.delay " ,
dstate_getinfo ( " ups.delay.shutdown " ) ) ;
2013-11-24 15:00:12 +00:00
}
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 ;
}
2022-06-29 10:37:36 +00:00
return instcmd ( " load.off.delay " ,
dstate_getinfo ( " ups.delay.shutdown " ) ) ;
2013-11-24 15:00:12 +00:00
}
upsdebugx ( 2 , " %s: command %s unavailable " , __func__ , cmdname ) ;
return STAT_INSTCMD_INVALID ;
}
2022-06-29 10:37:36 +00:00
/* If extradata is empty, use the default value
* from the QX to NUT table , if any */
2013-11-24 15:00:12 +00:00
extradata = extradata ? extradata : item - > dfl ;
snprintf ( value , sizeof ( value ) , " %s " , extradata ? extradata : " " ) ;
/* Preprocess command */
2022-06-29 10:37:36 +00:00
if ( item - > preprocess ! = NULL
& & item - > preprocess ( item , value , sizeof ( value ) )
) {
2013-11-24 15:00:12 +00:00
/* 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 ;
}
2022-06-29 10:37:36 +00:00
/* We got a reply from the UPS:
* either subdriver - > accepted ( - > command handled )
* or the command itself echoed back ( - > command failed )
*/
2013-11-24 15:00:12 +00:00
if ( strlen ( item - > value ) > 0 ) {
2022-06-29 10:37:36 +00:00
if ( subdriver - > accepted ! = NULL
& & ! strcasecmp ( item - > value , subdriver - > accepted )
) {
2013-11-24 15:00:12 +00:00
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 ;
}
2016-07-18 00:11:41 +00:00
/* See header file for details. */
2013-11-24 15:00:12 +00:00
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 ;
}
2022-06-29 10:37:36 +00:00
/* No NUT variable is available for this item, so we're handling
* a one - time setvar from ups . conf */
2013-11-24 15:00:12 +00:00
if ( item - > qxflags & QX_FLAG_NONUT ) {
const char * userval ;
/* Nothing to do */
if ( ! testvar ( item - > info_type ) ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 2 , " %s: nothing to do... [%s] " ,
__func__ , item - > info_type ) ;
2013-11-24 15:00:12 +00:00
return STAT_SET_HANDLED ;
}
userval = getval ( item - > info_type ) ;
2022-06-29 10:37:36 +00:00
upslogx ( LOG_INFO , " %s(%s, %s) " ,
__func__ , varname ,
userval ? userval : " [NULL] " ) ;
2013-11-24 15:00:12 +00:00
snprintf ( value , sizeof ( value ) , " %s " , userval ? userval : " " ) ;
/* This item is available in NUT */
} else {
2022-06-29 10:37:36 +00:00
upslogx ( LOG_INFO , " %s(%s, %s) " ,
__func__ , varname ,
strlen ( val ) ? val : " [NULL] " ) ;
2013-11-24 15:00:12 +00:00
if ( ! strlen ( val ) ) {
2022-06-29 10:37:36 +00:00
upslogx ( LOG_ERR , " %s: value not given for %s " ,
__func__ , item - > info_type ) ;
2013-11-24 15:00:12 +00:00
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 ) ) {
2022-06-29 10:37:36 +00:00
upslogx ( LOG_INFO , " %s: nothing to do... [%s] " ,
__func__ , item - > info_type ) ;
2013-11-24 15:00:12 +00:00
return STAT_SET_HANDLED ;
}
}
/* Check if given value is in the range of accepted values (range) */
if ( item - > qxflags & QX_FLAG_RANGE ) {
2022-06-29 10:37:36 +00:00
long valuetoset , min , max ;
2013-11-24 15:00:12 +00:00
if ( strspn ( value , " 0123456789 . " ) ! = strlen ( value ) ) {
2022-06-29 10:37:36 +00:00
upslogx ( LOG_ERR , " %s: non numerical value [%s: %s] " ,
__func__ , item - > info_type , value ) ;
2013-11-24 15:00:12 +00:00
return STAT_SET_UNKNOWN ; /* TODO: HANDLED but FAILED, not UNKNOWN! */
}
valuetoset = strtol ( value , NULL , 10 ) ;
2022-06-29 10:37:36 +00:00
/* No NUT var is available for this item, so
* take its range from qx2nut table */
2013-11-24 15:00:12 +00:00
if ( item - > qxflags & QX_FLAG_NONUT ) {
info_rw_t * rvalue ;
if ( ! strlen ( value ) ) {
2022-06-29 10:37:36 +00:00
upslogx ( LOG_ERR , " %s: value not given for %s " ,
__func__ , item - > info_type ) ;
2013-11-24 15:00:12 +00:00
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 + + ) {
2022-06-29 10:37:36 +00:00
if ( rvalue - > preprocess
& & rvalue - > preprocess ( rvalue - > value , sizeof ( rvalue - > value ) )
) {
2013-11-24 15:00:12 +00:00
continue ;
2022-06-29 10:37:36 +00:00
}
2013-11-24 15:00:12 +00:00
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 ;
}
2022-06-29 10:37:36 +00:00
/* We have a NUT var for this item, so check given value
* against the already set range */
2013-11-24 15:00:12 +00:00
} else {
const range_t * range = state_getrangelist ( root , item - > info_type ) ;
/* Unable to find tree node for var */
if ( ! range ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 2 , " %s: unable to find tree node for %s " ,
__func__ , item - > info_type ) ;
2013-11-24 15:00:12 +00:00
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 ) {
2022-06-29 10:37:36 +00:00
upslogx ( LOG_ERR , " %s: value out of range [%s: %s] " ,
__func__ , item - > info_type , value ) ;
2013-11-24 15:00:12 +00:00
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 ) {
2022-06-29 10:37:36 +00:00
/* No NUT var is available for this item, so
* take its range from qx2nut table */
2013-11-24 15:00:12 +00:00
if ( item - > qxflags & QX_FLAG_NONUT ) {
info_rw_t * envalue ;
if ( ! strlen ( value ) ) {
2022-06-29 10:37:36 +00:00
upslogx ( LOG_ERR , " %s: value not given for %s " ,
__func__ , item - > info_type ) ;
2013-11-24 15:00:12 +00:00
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 + + ) {
2022-06-29 10:37:36 +00:00
if ( envalue - > preprocess
& & envalue - > preprocess ( envalue - > value , sizeof ( envalue - > value ) )
) {
2013-11-24 15:00:12 +00:00
continue ;
2022-06-29 10:37:36 +00:00
}
2013-11-24 15:00:12 +00:00
if ( strcasecmp ( envalue - > value , value ) )
continue ;
/* value found */
ok = 1 ;
break ;
}
2022-06-29 10:37:36 +00:00
/* We have a NUT var for this item, so check given value
* against the already set range */
2013-11-24 15:00:12 +00:00
} else {
const enum_t * enumlist = state_getenumlist ( root , item - > info_type ) ;
/* Unable to find tree node for var */
if ( ! enumlist ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 2 , " %s: unable to find tree node for %s " ,
__func__ , item - > info_type ) ;
2013-11-24 15:00:12 +00:00
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 ) {
2022-06-29 10:37:36 +00:00
upslogx ( LOG_ERR , " %s: value out of range [%s: %s] " ,
__func__ , item - > info_type , value ) ;
2013-11-24 15:00:12 +00:00
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 ) {
2022-06-29 10:37:36 +00:00
const long aux = state_getaux ( root , item - > info_type ) ;
2013-11-24 15:00:12 +00:00
/* Unable to find tree node for var */
if ( aux < 0 ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 2 , " %s: unable to find tree node for %s " ,
__func__ , item - > info_type ) ;
2013-11-24 15:00:12 +00:00
return STAT_SET_UNKNOWN ;
}
2022-06-29 10:37:36 +00:00
/* FIXME? Should this cast to "long"?
* An int - size string is quite a lot already ,
* even on architectures with a moderate INTMAX
*/
2013-11-24 15:00:12 +00:00
if ( aux < ( int ) strlen ( value ) ) {
2022-06-29 10:37:36 +00:00
upslogx ( LOG_ERR , " %s: value is too long [%s: %s] " ,
__func__ , item - > info_type , value ) ;
2013-11-24 15:00:12 +00:00
return STAT_SET_UNKNOWN ; /* TODO: HANDLED but FAILED, not UNKNOWN! */
}
}
/* Preprocess value: from NUT-compliant to UPS-compliant */
2022-06-29 10:37:36 +00:00
if ( item - > preprocess ! = NULL
& & item - > preprocess ( item , value , sizeof ( value ) )
) {
2013-11-24 15:00:12 +00:00
/* 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 ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 2 , " %s: setting server side variable %s " ,
__func__ , item - > info_type ) ;
2013-11-24 15:00:12 +00:00
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! */
}
2022-06-29 10:37:36 +00:00
/* We got a reply from the UPS:
* either subdriver - > accepted ( - > command handled )
* or the command itself echoed back ( - > command failed ) */
2013-11-24 15:00:12 +00:00
if ( strlen ( item - > value ) > 0 ) {
2022-06-29 10:37:36 +00:00
if ( subdriver - > accepted ! = NULL
& & ! strcasecmp ( item - > value , subdriver - > accepted )
) {
2013-11-24 15:00:12 +00:00
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 */
2022-06-29 10:37:36 +00:00
void upsdrv_shutdown ( void )
__attribute__ ( ( noreturn ) ) ;
2013-11-24 15:00:12 +00:00
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 ;
}
}
2022-06-29 10:37:36 +00:00
fatalx ( EXIT_SUCCESS , " Shutting down in %s seconds " ,
dstate_getinfo ( " ups.delay.shutdown " ) ) ;
2013-11-24 15:00:12 +00:00
}
fatalx ( EXIT_FAILURE , " Shutdown failed! " ) ;
}
2022-06-29 10:37:36 +00:00
# ifdef QX_USB
# ifndef TESTING
static const struct {
const char * name ;
int ( * command ) ( const char * cmd , char * buf , size_t buflen ) ;
} usbsubdriver [ ] = {
{ " cypress " , & cypress_command } ,
{ " phoenixtec " , & phoenixtec_command } ,
{ " phoenix " , & phoenix_command } ,
{ " ippon " , & ippon_command } ,
{ " krauler " , & krauler_command } ,
{ " fabula " , & fabula_command } ,
{ " hunnox " , & hunnox_command } ,
{ " fuji " , & fuji_command } ,
{ " sgs " , & sgs_command } ,
{ " snr " , & snr_command } ,
{ " ablerex " , & ablerex_command } ,
{ " armac " , & armac_command } ,
{ NULL , NULL }
} ;
# endif
# endif
2013-11-24 15:00:12 +00:00
void upsdrv_help ( void )
{
2022-06-29 10:37:36 +00:00
# ifdef QX_USB
# ifndef TESTING
printf ( " \n Acceptable values for 'subdriver' via -x or ups.conf in this driver: " ) ;
size_t i ;
for ( i = 0 ; usbsubdriver [ i ] . name ! = NULL ; i + + ) {
if ( i > 0 )
printf ( " , " ) ;
printf ( " %s " , usbsubdriver [ i ] . name ) ;
}
printf ( " \n \n " ) ;
# endif
# endif
2013-11-24 15:00:12 +00:00
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__ ) ;
2022-06-29 10:37:36 +00:00
snprintf ( temp , sizeof ( temp ) ,
" Set shutdown delay, in seconds (default=%s) " , DEFAULT_OFFDELAY ) ;
2013-11-24 15:00:12 +00:00
addvar ( VAR_VALUE , QX_VAR_OFFDELAY , temp ) ;
2022-06-29 10:37:36 +00:00
snprintf ( temp , sizeof ( temp ) ,
" Set startup delay, in seconds (default=%s) " , DEFAULT_ONDELAY ) ;
2013-11-24 15:00:12 +00:00
addvar ( VAR_VALUE , QX_VAR_ONDELAY , temp ) ;
2022-06-29 10:37:36 +00:00
addvar ( VAR_FLAG , " stayoff " ,
" If invoked the UPS won't return after a shutdown when FSD arises " ) ;
2013-11-24 15:00:12 +00:00
2022-06-29 10:37:36 +00:00
snprintf ( temp , sizeof ( temp ) ,
" Set polling frequency, in seconds, to reduce data flow (default=%d) " ,
DEFAULT_POLLFREQ ) ;
2013-11-24 15:00:12 +00:00
addvar ( VAR_VALUE , QX_VAR_POLLFREQ , temp ) ;
2022-06-29 10:37:36 +00:00
addvar ( VAR_VALUE , " protocol " ,
" Preselect communication protocol (skip autodetection) " ) ;
2013-11-24 15:00:12 +00:00
/* battery.{charge,runtime} guesstimation */
2022-06-29 10:37:36 +00:00
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 " ) ;
2013-11-24 15:00:12 +00:00
# ifdef QX_USB
addvar ( VAR_VALUE , " subdriver " , " Serial-over-USB subdriver selection " ) ;
2015-04-30 13:53:36 +00:00
/* allow -x vendor=X, vendorid=X, product=X, productid=X, serial=X */
nut_usb_addvars ( ) ;
2013-11-24 15:00:12 +00:00
2022-06-29 10:37:36 +00:00
addvar ( VAR_VALUE , " langid_fix " ,
" Apply the language ID workaround to the krauler subdriver "
" (0x409 or 0x4095) " ) ;
addvar ( VAR_FLAG , " noscanlangid " , " Don't autoscan valid range for langid " ) ;
2013-11-24 15:00:12 +00:00
# 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 ( ) ;
2022-06-29 10:37:36 +00:00
/* Do a full update (polling) every pollfreq or upon data change
* ( i . e . setvar / instcmd ) */
2013-11-24 15:00:12 +00:00
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 ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 1 ,
" Communications with the UPS lost: status read failed! " ) ;
2013-11-24 15:00:12 +00:00
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 ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 1 ,
" Communications with the UPS lost: status read failed! " ) ;
2013-11-24 15:00:12 +00:00
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 ) ;
}
}
2022-06-29 10:37:36 +00:00
if ( ! find_nut_info ( " load.off " , QX_FLAG_CMD , QX_FLAG_SKIP )
& & find_nut_info ( " load.off.delay " , QX_FLAG_CMD , QX_FLAG_SKIP )
) {
2013-11-24 15:00:12 +00:00
/* Adds default with a delay value of '0' (= immediate) */
dstate_addcmd ( " load.off " ) ;
}
2022-06-29 10:37:36 +00:00
if ( ! find_nut_info ( " load.on " , QX_FLAG_CMD , QX_FLAG_SKIP )
& & find_nut_info ( " load.on.delay " , QX_FLAG_CMD , QX_FLAG_SKIP )
) {
2013-11-24 15:00:12 +00:00
/* 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 ) ;
2022-06-29 10:37:36 +00:00
dstate_setinfo ( " driver.parameter.pollfreq " , " %ld " , pollfreq ) ;
2013-11-24 15:00:12 +00:00
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 } ,
2022-06-29 10:37:36 +00:00
{ NULL , 0 , 0 }
2013-11-24 15:00:12 +00:00
} ;
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
2022-06-29 10:37:36 +00:00
warn_if_bad_usb_port_filename ( device_path ) ;
2013-11-24 15:00:12 +00:00
2022-06-29 10:37:36 +00:00
# ifndef TESTING
2013-11-24 15:00:12 +00:00
int ret , langid ;
char tbuf [ 255 ] ; /* Some devices choke on size > 255 */
2022-06-29 10:37:36 +00:00
char * regex_array [ 7 ] ;
2013-11-24 15:00:12 +00:00
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 " ) ;
2022-06-29 10:37:36 +00:00
regex_array [ 6 ] = getval ( " device " ) ;
2013-11-24 15:00:12 +00:00
/* Check for language ID workaround (#1) */
if ( getval ( " langid_fix " ) ) {
/* Skip "0x" prefix and set back to hexadecimal */
2022-06-29 10:37:36 +00:00
unsigned int u_langid_fix ;
if ( ( sscanf ( getval ( " langid_fix " ) + 2 , " %x " , & u_langid_fix ) ! = 1 )
| | ( u_langid_fix > INT_MAX )
) {
2013-11-24 15:00:12 +00:00
upslogx ( LOG_NOTICE , " Error enabling language ID workaround " ) ;
} else {
2022-06-29 10:37:36 +00:00
langid_fix = ( int ) u_langid_fix ;
upsdebugx ( 2 ,
" Language ID workaround enabled (using '0x%x') " ,
langid_fix ) ;
2013-11-24 15:00:12 +00:00
}
}
/* Pick up the subdriver name if set explicitly */
if ( subdrv ) {
int i ;
if ( ! regex_array [ 0 ] | | ! regex_array [ 1 ] ) {
2022-06-29 10:37:36 +00:00
fatalx ( EXIT_FAILURE ,
" When specifying a subdriver, "
" 'vendorid' and 'productid' are mandatory. " ) ;
2013-11-24 15:00:12 +00:00
}
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 ) ;
}
}
2022-06-29 10:37:36 +00:00
ret = USBNewRegexMatcher ( & regex_matcher ,
regex_array ,
REG_ICASE | REG_EXTENDED ) ;
2013-11-24 15:00:12 +00:00
switch ( ret )
{
case - 1 :
fatal_with_errno ( EXIT_FAILURE , " USBNewRegexMatcher " ) ;
case 0 :
break ; /* All is well */
default :
2022-06-29 10:37:36 +00:00
fatalx ( EXIT_FAILURE ,
" Invalid regular expression: %s " ,
regex_array [ ret ] ) ;
2013-11-24 15:00:12 +00:00
}
/* Link the matchers */
regex_matcher - > next = & device_matcher ;
ret = usb - > open ( & udev , & usbdevice , regex_matcher , NULL ) ;
if ( ret < 0 ) {
fatalx ( EXIT_FAILURE ,
2022-06-29 10:37:36 +00:00
" 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 "
2013-11-24 15:00:12 +00:00
" (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) */
2022-06-29 10:37:36 +00:00
if ( ( langid_fix ! = - 1 ) & & ( ! getval ( " noscanlangid " ) ) ) {
2013-11-24 15:00:12 +00:00
/* Future improvement:
2022-06-29 10:37:36 +00:00
* Asking for the zero ' th index is special - it returns
* a string descriptor that contains all the language
* IDs supported by the device .
2013-11-24 15:00:12 +00:00
* Typically there aren ' t many - often only one .
2022-06-29 10:37:36 +00:00
* 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 .
2013-11-24 15:00:12 +00:00
* This should allow automatic application of the workaround */
2022-06-29 10:37:36 +00:00
ret = usb_get_string ( udev , 0 , 0 ,
( usb_ctrl_charbuf ) tbuf , sizeof ( tbuf ) ) ;
2013-11-24 15:00:12 +00:00
if ( ret > = 4 ) {
2022-06-29 10:37:36 +00:00
langid = ( ( uint8_t ) tbuf [ 2 ] ) | ( ( ( uint8_t ) tbuf [ 3 ] ) < < 8 ) ;
upsdebugx ( 1 ,
" First supported language ID: 0x%x "
" (please report to the NUT maintainer!) " ,
langid ) ;
2013-11-24 15:00:12 +00:00
}
}
# 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 ) ;
2022-06-29 10:37:36 +00:00
free ( usbdevice . Device ) ;
2013-11-24 15:00:12 +00:00
# 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 . */
2022-06-29 10:37:36 +00:00
static ssize_t qx_command ( const char * cmd , char * buf , size_t buflen )
2013-11-24 15:00:12 +00:00
{
2022-06-29 10:37:36 +00:00
/* NOTE: Could not find in which ifdef-ed codepath, but clang complained
* about unused parameters here . Reference them just in case . . .
*/
NUT_UNUSED_VARIABLE ( cmd ) ;
NUT_UNUSED_VARIABLE ( buf ) ;
NUT_UNUSED_VARIABLE ( buflen ) ;
2013-11-24 15:00:12 +00:00
# ifndef TESTING
2022-06-29 10:37:36 +00:00
ssize_t ret = - 1 ;
2013-11-24 15:00:12 +00:00
2022-06-29 10:37:36 +00:00
# ifdef QX_USB
2013-11-24 15:00:12 +00:00
2022-06-29 10:37:36 +00:00
# ifdef QX_SERIAL
2013-11-24 15:00:12 +00:00
/* Communication: USB */
if ( is_usb ) {
2022-06-29 10:37:36 +00:00
# endif /* QX_SERIAL (&& QX_USB)*/
2013-11-24 15:00:12 +00:00
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 )
{
2022-06-29 10:37:36 +00:00
case ERROR_BUSY : /* Device or resource busy */
2013-11-24 15:00:12 +00:00
fatal_with_errno ( EXIT_FAILURE , " Got disconnected by another driver " ) ;
2022-06-29 10:37:36 +00:00
# ifndef HAVE___ATTRIBUTE__NORETURN
# if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wunreachable-code"
# endif
exit ( EXIT_FAILURE ) ; /* Should not get here in practice, but compiler is afraid we can fall through */
# if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE)
# pragma GCC diagnostic pop
# endif
# endif
2013-11-24 15:00:12 +00:00
2022-06-29 10:37:36 +00:00
# if WITH_LIBUSB_0_1 /* limit to libusb 0.1 implementation */
2013-11-24 15:00:12 +00:00
case - EPERM : /* Operation not permitted */
fatal_with_errno ( EXIT_FAILURE , " Permissions problem " ) ;
2022-06-29 10:37:36 +00:00
# ifndef HAVE___ATTRIBUTE__NORETURN
# if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wunreachable-code"
# endif
exit ( EXIT_FAILURE ) ; /* Should not get here in practice, but compiler is afraid we can fall through */
# if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE)
# pragma GCC diagnostic pop
# endif
# endif
# endif /* WITH_LIBUSB_0_1 */
2013-11-24 15:00:12 +00:00
2022-06-29 10:37:36 +00:00
case ERROR_PIPE : /* Broken pipe */
2013-11-24 15:00:12 +00:00
if ( usb_clear_halt ( udev , 0x81 ) = = 0 ) {
upsdebugx ( 1 , " Stall condition cleared " ) ;
break ;
}
2022-06-29 10:37:36 +00:00
# if (defined ETIME) && ETIME && WITH_LIBUSB_0_1 /* limit to libusb 0.1 implementation */
goto fallthrough_case_ETIME ;
2013-11-24 15:00:12 +00:00
case - ETIME : /* Timer expired */
2022-06-29 10:37:36 +00:00
fallthrough_case_ETIME :
# endif /* ETIME && WITH_LIBUSB_0_1 */
2013-11-24 15:00:12 +00:00
if ( usb_reset ( udev ) = = 0 ) {
upsdebugx ( 1 , " Device reset handled " ) ;
}
2022-06-29 10:37:36 +00:00
goto fallthrough_case_reconnect ;
case ERROR_NO_DEVICE : /* No such device */
case ERROR_ACCESS : /* Permission denied */
case ERROR_IO : /* I/O error */
# if WITH_LIBUSB_0_1 /* limit to libusb 0.1 implementation */
2013-11-24 15:00:12 +00:00
case - ENXIO : /* No such device or address */
2022-06-29 10:37:36 +00:00
# endif /* WITH_LIBUSB_0_1 */
case ERROR_NOT_FOUND : /* No such file or directory */
fallthrough_case_reconnect :
2013-11-24 15:00:12 +00:00
/* Uh oh, got to reconnect! */
usb - > close ( udev ) ;
udev = NULL ;
break ;
2022-06-29 10:37:36 +00:00
case ERROR_TIMEOUT : /* Connection timed out */
case ERROR_OVERFLOW : /* Value too large for defined data type */
# if EPROTO && WITH_LIBUSB_0_1 /* limit to libusb 0.1 implementation */
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 ;
}
2022-06-29 10:37:36 +00:00
# ifdef QX_SERIAL
2013-11-24 15:00:12 +00:00
/* Communication: serial */
} else { /* !is_usb */
2022-06-29 10:37:36 +00:00
# endif /* QX_SERIAL (&& QX_USB) */
2013-11-24 15:00:12 +00:00
2022-06-29 10:37:36 +00:00
# endif /* QX_USB (&& TESTING) */
2013-11-24 15:00:12 +00:00
2022-06-29 10:37:36 +00:00
# ifdef QX_SERIAL
2013-11-24 15:00:12 +00:00
ser_flush_io ( upsfd ) ;
ret = ser_send ( upsfd , " %s " , cmd ) ;
if ( ret < = 0 ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 3 , " send: %s (%zd) " ,
ret ? strerror ( errno ) : " timeout " , ret ) ;
2013-11-24 15:00:12 +00:00
return ret ;
}
2022-06-29 10:37:36 +00:00
upsdebugx ( 3 , " send: '%.*s' " ,
( int ) strcspn ( cmd , " \r " ) , cmd ) ;
2013-11-24 15:00:12 +00:00
ret = ser_get_buf ( upsfd , buf , buflen , SER_WAIT_SEC , 0 ) ;
if ( ret < = 0 ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 3 , " read: %s (%zd) " ,
ret ? strerror ( errno ) : " timeout " , ret ) ;
2013-11-24 15:00:12 +00:00
return ret ;
}
2022-06-29 10:37:36 +00:00
upsdebug_hex ( 5 , " read " , buf , ( size_t ) ret ) ;
2013-11-24 15:00:12 +00:00
upsdebugx ( 3 , " read: '%.*s' " , ( int ) strcspn ( buf , " \r " ) , buf ) ;
2022-06-29 10:37:36 +00:00
# ifdef QX_USB
2013-11-24 15:00:12 +00:00
} /* !is_usb */
2022-06-29 10:37:36 +00:00
# endif /* QX_USB (&& QX_SERIAL) */
2013-11-24 15:00:12 +00:00
2022-06-29 10:37:36 +00:00
# endif /* QX_SERIAL (&& TESTING) */
2013-11-24 15:00:12 +00:00
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 ;
}
2022-06-29 10:37:36 +00:00
upsdebugx ( 3 , " read: '%.*s' " ,
( int ) strcspn ( testing [ i ] . answer , " \r " ) ,
testing [ i ] . answer ) ;
2015-04-30 13:53:36 +00:00
/* If requested to do so and this is the case, try to preserve inner '\0's (treat answer as a sequence of bytes) */
if ( testing [ i ] . answer_len > 0 & & strlen ( testing [ i ] . answer ) < ( size_t ) testing [ i ] . answer_len ) {
size_t len ;
len = buflen < = ( size_t ) testing [ i ] . answer_len ? buflen - 1 : ( size_t ) testing [ i ] . answer_len ;
len = len < = sizeof ( testing [ i ] . answer ) ? len : sizeof ( testing [ i ] . answer ) ;
memcpy ( buf , testing [ i ] . answer , len ) ;
upsdebug_hex ( 4 , " read " , buf , ( int ) len ) ;
return len ;
}
2013-11-24 15:00:12 +00:00
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.. */
2022-06-29 10:37:36 +00:00
upsdebugx ( 3 , " read: '%.*s' " ,
( int ) strcspn ( subdriver - > rejected , " \r " ) ,
subdriver - > rejected ) ;
2013-11-24 15:00:12 +00:00
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 */
}
2016-07-18 00:11:41 +00:00
/* See header file for details.
2013-11-24 15:00:12 +00:00
* 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 ;
}
2022-06-29 10:37:36 +00:00
upsdebugx ( 5 , " %s: Warning! %s not in list of known values " ,
__func__ , value ) ;
2013-11-24 15:00:12 +00:00
}
/* 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 */
2022-06-29 10:37:36 +00:00
snprintf ( subdrv_name , sizeof ( subdrv_name ) , " %.*s " ,
( int ) strcspn ( subdriver_list [ i ] - > name , " " ) ,
subdriver_list [ i ] - > name ) ;
2013-11-24 15:00:12 +00:00
if ( strcasecmp ( subdrv_name , protocol ) ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 2 , " Skipping protocol %s " ,
subdriver_list [ i ] - > name ) ;
2013-11-24 15:00:12 +00:00
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 + + ) {
2022-06-29 10:37:36 +00:00
if ( envalue - > preprocess
& & envalue - > preprocess ( envalue - > value , sizeof ( envalue - > value ) )
) {
2013-11-24 15:00:12 +00:00
continue ;
2022-06-29 10:37:36 +00:00
}
2013-11-24 15:00:12 +00:00
/* 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 )
2022-06-29 10:37:36 +00:00
upslogx ( LOG_INFO , " %s, settable values:%s " ,
item - > info_type ,
strlen ( buf ) > 0 ? buf : " none " ) ;
2013-11-24 15:00:12 +00:00
}
/* 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 + + ) {
2022-06-29 10:37:36 +00:00
if ( rvalue - > preprocess
& & rvalue - > preprocess ( rvalue - > value , sizeof ( rvalue - > value ) )
) {
2013-11-24 15:00:12 +00:00
continue ;
2022-06-29 10:37:36 +00:00
}
2013-11-24 15:00:12 +00:00
if ( ! from ) {
from = rvalue ;
continue ;
}
to = rvalue ;
2022-06-29 10:37:36 +00:00
/* This item is not available yet in NUT, so
* publish these data in the logs */
2013-11-24 15:00:12 +00:00
if ( item - > qxflags & QX_FLAG_NONUT ) {
2022-06-29 10:37:36 +00:00
upslogx ( LOG_INFO , " %s, settable range: %s..%s " ,
item - > info_type , from - > value , to - > value ) ;
2013-11-24 15:00:12 +00:00
ok + + ;
/* This item is available in NUT, add its range to the variable */
} else {
2022-06-29 10:37:36 +00:00
long lFrom = strtol ( from - > value , NULL , 10 ) ,
lTo = strtol ( to - > value , NULL , 10 ) ;
if ( lFrom > INT_MAX | | lTo > INT_MAX ) {
upslogx ( LOG_INFO ,
" %s, settable range exceeds INT_MAX: %ld..%ld " ,
item - > info_type , lFrom , lTo ) ;
} else {
dstate_addrange ( item - > info_type , ( int ) lFrom , ( int ) lTo ) ;
}
2013-11-24 15:00:12 +00:00
}
from = NULL ;
to = NULL ;
}
2022-06-29 10:37:36 +00:00
/* This item is not available yet in NUT and we weren't able to
* get its range ; let people know it */
2013-11-24 15:00:12 +00:00
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 ;
}
2015-04-30 13:53:36 +00:00
/* Clear data from previous_item */
memset ( previous_item . command , 0 , sizeof ( previous_item . command ) ) ;
memset ( previous_item . answer , 0 , sizeof ( previous_item . answer ) ) ;
2022-06-29 10:37:36 +00:00
/* 3 modes: QX_WALKMODE_INIT, QX_WALKMODE_QUICK_UPDATE
* and QX_WALKMODE_FULL_UPDATE */
2013-11-24 15:00:12 +00:00
/* 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 */
2022-06-29 10:37:36 +00:00
if ( ! strncmp ( item - > info_type , " ups.alarm " , 9 )
| | ! strncmp ( item - > info_type , " ups.status " , 10 )
) {
2013-11-24 15:00:12 +00:00
break ;
2022-06-29 10:37:36 +00:00
}
2013-11-24 15:00:12 +00:00
/* 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) */
2022-06-29 10:37:36 +00:00
if ( ( item - > qxflags & QX_FLAG_SEMI_STATIC )
& & ( data_has_changed = = FALSE )
) {
2013-11-24 15:00:12 +00:00
continue ;
2022-06-29 10:37:36 +00:00
}
2013-11-24 15:00:12 +00:00
break ;
2022-06-29 10:37:36 +00:00
# if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) )
# pragma GCC diagnostic push
# endif
# ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT
# pragma GCC diagnostic ignored "-Wcovered-switch-default"
# endif
# ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE
# pragma GCC diagnostic ignored "-Wunreachable-code"
# endif
/* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */
# ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wunreachable-code"
# pragma clang diagnostic ignored "-Wcovered-switch-default"
# endif
/* All enum cases defined as of the time of coding
* have been covered above . Handle later definitions ,
* memory corruptions and buggy inputs below . . .
*/
2013-11-24 15:00:12 +00:00
default :
fatalx ( EXIT_FAILURE , " %s: unknown update mode! " , __func__ ) ;
2022-06-29 10:37:36 +00:00
# ifdef __clang__
# pragma clang diagnostic pop
# endif
# if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) )
# pragma GCC diagnostic pop
# endif
2013-11-24 15:00:12 +00:00
}
/* 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 ;
}
2022-06-29 10:37:36 +00:00
/* 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 )
) {
2013-11-24 15:00:12 +00:00
2022-06-29 10:37:36 +00:00
snprintf ( item - > answer , sizeof ( item - > answer ) , " %s " ,
previous_item . answer ) ;
2013-11-24 15:00:12 +00:00
/* Process the answer */
retcode = qx_process_answer ( item , strlen ( item - > answer ) ) ;
/* ..otherwise: execute command to get answer from the UPS */
2015-04-30 13:53:36 +00:00
} else {
2013-11-24 15:00:12 +00:00
retcode = qx_process ( item , NULL ) ;
2015-04-30 13:53:36 +00:00
}
2013-11-24 15:00:12 +00:00
/* Record item as previous_item */
2022-06-29 10:37:36 +00:00
snprintf ( previous_item . command , sizeof ( previous_item . command ) , " %s " ,
item - > command ) ;
snprintf ( previous_item . answer , sizeof ( previous_item . answer ) , " %s " ,
item - > answer ) ;
2013-11-24 15:00:12 +00:00
if ( retcode ) {
2015-04-30 13:53:36 +00:00
/* Clear data from the item */
memset ( item - > answer , 0 , sizeof ( item - > answer ) ) ;
memset ( item - > value , 0 , sizeof ( item - > value ) ) ;
2013-11-24 15:00:12 +00:00
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 ;
/* Don't know what happened, try again later... */
continue ;
}
2022-06-29 10:37:36 +00:00
/* Process the value we got back (set status bits
* and set the value of other parameters ) */
2013-11-24 15:00:12 +00:00
retcode = ups_infoval_set ( item ) ;
/* Clear data from the item */
2015-04-30 13:53:36 +00:00
memset ( item - > answer , 0 , sizeof ( item - > answer ) ) ;
memset ( item - > value , 0 , sizeof ( item - > value ) ) ;
2013-11-24 15:00:12 +00:00
/* Uh-oh! Some error! */
2015-04-30 13:53:36 +00:00
if ( retcode = = - 1 ) {
if ( item - > qxflags & QX_FLAG_QUICK_POLL )
return FALSE ;
2013-11-24 15:00:12 +00:00
continue ;
2015-04-30 13:53:36 +00:00
}
2022-06-29 10:37:36 +00:00
/* Set var flags/range/enum (not for ups.{alarm.status},
* hence the retcode check ) */
2013-11-24 15:00:12 +00:00
if ( retcode & & mode = = QX_WALKMODE_INIT ) {
qx_set_var ( item ) ;
}
}
/* Update battery guesstimation */
2022-06-29 10:37:36 +00:00
if ( mode = = QX_WALKMODE_FULL_UPDATE
& & ( d_equal ( batt . runt . act , - 1 ) | | d_equal ( batt . chrg . act , - 1 ) )
) {
2013-11-24 15:00:12 +00:00
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 ;
}
}
2022-06-29 10:37:36 +00:00
const char * val = dstate_getinfo ( " battery.voltage " ) ;
if ( ! val ) {
upsdebugx ( 2 , " %s: unable to get battery.voltage " , __func__ ) ;
} else {
/* For age-corrected estimates below,
* see theory and experimental graphs at
* https : //github.com/networkupstools/nut/pull/1027
*/
batt . volt . act = batt . packs * strtod ( val , NULL ) ;
if ( batt . volt . act > 0 & & batt . volt . low > 0 & & batt . volt . high > batt . volt . low ) {
2013-11-24 15:00:12 +00:00
2022-06-29 10:37:36 +00:00
double voltage_battery_charge = ( batt . volt . act - batt . volt . low ) / ( batt . volt . high - batt . volt . low ) ;
if ( voltage_battery_charge < 0 ) {
voltage_battery_charge = 0 ;
}
if ( voltage_battery_charge > 1 ) {
voltage_battery_charge = 1 ;
}
/* Correct estimated runtime remaining for old batteries:
* this value replacement only happens if the actual
* voltage_battery_charge is smaller than expected by
* previous ( load - based ) estimation , thus adapting to a
* battery too old and otherwise behaving non - linearly
*/
if ( voltage_battery_charge < ( batt . runt . est / batt . runt . nom ) ) {
double estPrev = batt . runt . est ;
batt . runt . est = voltage_battery_charge * batt . runt . nom ;
upsdebugx ( 3 , " %s: updating batt.runt.est from '%g' to '%g' " ,
__func__ , estPrev , batt . runt . est ) ;
}
}
}
if ( d_equal ( batt . chrg . act , - 1 ) )
dstate_setinfo ( " battery.charge " , " %.0f " ,
100 * batt . runt . est / batt . runt . nom ) ;
if ( d_equal ( batt . runt . act , - 1 ) & & ! qx_load ( ) )
dstate_setinfo ( " battery.runtime " , " %.0f " ,
batt . runt . est / load . eff ) ;
2013-11-24 15:00:12 +00:00
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 */
}
}
2016-07-18 00:11:41 +00:00
/* See header file for details. */
2013-11-24 15:00:12 +00:00
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 */
2022-06-29 10:37:36 +00:00
static int qx_process_answer ( item_t * item , const size_t len )
2013-11-24 15:00:12 +00:00
{
/* Query rejected by the UPS */
if ( subdriver - > rejected & & ! strcasecmp ( item - > answer , subdriver - > rejected ) ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 2 , " %s: query rejected by the UPS (%s) " ,
__func__ , item - > info_type ) ;
2013-11-24 15:00:12 +00:00
return - 1 ;
}
/* Short reply */
if ( item - > answer_len & & len < item - > answer_len ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 2 , " %s: short reply (%s) " ,
__func__ , item - > info_type ) ;
2013-11-24 15:00:12 +00:00
return - 1 ;
}
/* Wrong leading character */
if ( item - > leading & & item - > answer [ 0 ] ! = item - > leading ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 2 ,
" %s: %s - invalid start character [%02x], expected [%02x] " ,
__func__ , item - > info_type , item - > answer [ 0 ] , item - > leading ) ;
2013-11-24 15:00:12 +00:00
return - 1 ;
}
2016-07-18 00:11:41 +00:00
/* Check boundaries */
if ( item - > to & & item - > to < item - > from ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 1 ,
" %s: in %s, starting char's position (%d) "
" follows ending char's one (%d) " ,
__func__ , item - > info_type , item - > from , item - > to ) ;
2016-07-18 00:11:41 +00:00
return - 1 ;
}
2013-11-24 15:00:12 +00:00
/* Get value */
if ( strlen ( item - > answer ) ) {
2022-06-29 10:37:36 +00:00
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 ) ;
2013-11-24 15:00:12 +00:00
} else {
snprintf ( item - > value , sizeof ( item - > value ) , " %s " , " " ) ;
}
return 0 ;
}
2016-07-18 00:11:41 +00:00
/* See header file for details. */
2013-11-24 15:00:12 +00:00
int qx_process ( item_t * item , const char * command )
{
2022-06-29 10:37:36 +00:00
char buf [ sizeof ( item - > answer ) - 1 ] = " " , * cmd ;
ssize_t len ;
size_t cmdlen = command ?
( strlen ( command ) > = SMALLBUF ? strlen ( command ) + 1 : SMALLBUF ) :
( item - > command & & strlen ( item - > command ) > = SMALLBUF ? strlen ( item - > command ) + 1 : SMALLBUF ) ;
size_t cmdsz = ( sizeof ( char ) * cmdlen ) ; /* in bytes, to be pedantic */
if ( ! ( cmd = xmalloc ( cmdsz ) ) ) {
upslogx ( LOG_ERR , " qx_process() failed to allocate buffer " ) ;
return - 1 ;
}
2016-07-18 00:11:41 +00:00
/* Prepare the command to be used */
2022-06-29 10:37:36 +00:00
memset ( cmd , 0 , cmdsz ) ;
snprintf ( cmd , cmdsz , " %s " , command ? command : item - > command ) ;
2016-07-18 00:11:41 +00:00
/* Preprocess the command */
if (
item - > preprocess_command ! = NULL & &
2022-06-29 10:37:36 +00:00
item - > preprocess_command ( item , cmd , cmdsz ) = = - 1
2016-07-18 00:11:41 +00:00
) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 4 , " %s: failed to preprocess command [%s] " ,
__func__ , item - > info_type ) ;
free ( cmd ) ;
2016-07-18 00:11:41 +00:00
return - 1 ;
}
2013-11-24 15:00:12 +00:00
/* Send the command */
2016-07-18 00:11:41 +00:00
len = qx_command ( cmd , buf , sizeof ( buf ) ) ;
2013-11-24 15:00:12 +00:00
2015-04-30 13:53:36 +00:00
memset ( item - > answer , 0 , sizeof ( item - > answer ) ) ;
2022-06-29 10:37:36 +00:00
if ( len < 0 | | len > INT_MAX ) {
upsdebugx ( 4 , " %s: failed to preprocess answer [%s] " ,
__func__ , item - > info_type ) ;
free ( cmd ) ;
return - 1 ;
}
2016-07-18 00:11:41 +00:00
memcpy ( item - > answer , buf , sizeof ( buf ) ) ;
2015-04-30 13:53:36 +00:00
/* Preprocess the answer */
if ( item - > preprocess_answer ! = NULL ) {
2022-06-29 10:37:36 +00:00
len = item - > preprocess_answer ( item , ( int ) len ) ;
if ( len < 0 | | len > INT_MAX ) {
upsdebugx ( 4 , " %s: failed to preprocess answer [%s] " ,
__func__ , item - > info_type ) ;
/* Clear the failed answer, preventing it from
* being reused by next items with same command */
2016-07-18 00:11:41 +00:00
memset ( item - > answer , 0 , sizeof ( item - > answer ) ) ;
2022-06-29 10:37:36 +00:00
free ( cmd ) ;
2015-04-30 13:53:36 +00:00
return - 1 ;
}
}
2013-11-24 15:00:12 +00:00
2022-06-29 10:37:36 +00:00
free ( cmd ) ;
2013-11-24 15:00:12 +00:00
/* Process the answer to get the value */
2022-06-29 10:37:36 +00:00
return qx_process_answer ( item , ( size_t ) len ) ;
2013-11-24 15:00:12 +00:00
}
2016-07-18 00:11:41 +00:00
/* See header file for details. */
2013-11-24 15:00:12 +00:00
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 ) ) ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 4 , " %s: failed to preprocess value [%s: %s] " ,
__func__ , item - > info_type , item - > value ) ;
2013-11-24 15:00:12 +00:00
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 ) ;
2022-06-29 10:37:36 +00:00
/* Cover most of the cases: either left/right filled with hashes,
* spaces or a mix of both */
2015-04-30 13:53:36 +00:00
if ( item - > qxflags & QX_FLAG_TRIM )
2016-07-18 00:11:41 +00:00
str_trim_m ( value , " # " ) ;
2013-11-24 15:00:12 +00:00
if ( strcasecmp ( item - > dfl , " %s " ) ) {
if ( strspn ( value , " 0123456789 . " ) ! = strlen ( value ) ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 2 , " %s: non numerical value [%s: %s] " ,
__func__ , item - > info_type , value ) ;
2013-11-24 15:00:12 +00:00
return - 1 ;
}
2022-06-29 10:37:36 +00:00
# ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
# pragma GCC diagnostic push
# endif
# ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
# pragma GCC diagnostic ignored "-Wformat-nonliteral"
# endif
# ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY
# pragma GCC diagnostic ignored "-Wformat-security"
# endif
2013-11-24 15:00:12 +00:00
snprintf ( value , sizeof ( value ) , item - > dfl , strtod ( value , NULL ) ) ;
2022-06-29 10:37:36 +00:00
# ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
# pragma GCC diagnostic pop
# endif
2013-11-24 15:00:12 +00:00
}
}
if ( item - > qxflags & QX_FLAG_NONUT ) {
upslogx ( LOG_INFO , " %s: %s " , item - > info_type , value ) ;
return 1 ;
}
if ( ! strlen ( value ) ) {
2022-06-29 10:37:36 +00:00
upsdebugx ( 1 , " %s: non significant value [%s] " ,
__func__ , item - > info_type ) ;
2013-11-24 15:00:12 +00:00
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 ;
}
2016-07-18 00:11:41 +00:00
/* See header file for details. */
2022-06-29 10:37:36 +00:00
unsigned int qx_status ( void )
2013-11-24 15:00:12 +00:00
{
return ups_status ;
}