803 lines
27 KiB
C
803 lines
27 KiB
C
/* nutdrv_qx_bestups.c - Subdriver for Best Power/Sola Australia UPSes
|
|
*
|
|
* Copyright (C)
|
|
* 2014 Daniele Pezzini <hyouko@gmail.com>
|
|
* Based on:
|
|
* bestups.c - Copyright (C)
|
|
* 1999 Russell Kroll <rkroll@exploits.org>
|
|
* Jason White <jdwhite@jdwhite.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
|
|
#include "main.h"
|
|
#include "nut_float.h"
|
|
#include "nut_stdint.h"
|
|
#include "nutdrv_qx.h"
|
|
#include "nutdrv_qx_blazer-common.h"
|
|
#include "nutdrv_qx_bestups.h"
|
|
|
|
#define BESTUPS_VERSION "BestUPS 0.06"
|
|
|
|
/* Support functions */
|
|
static int bestups_claim(void);
|
|
static void bestups_initups(void);
|
|
static void bestups_makevartable(void);
|
|
|
|
/* Answer preprocess functions */
|
|
static int bestups_preprocess_id_answer(item_t *item, const int len);
|
|
|
|
/* Preprocess functions */
|
|
static int bestups_process_setvar(item_t *item, char *value, const size_t valuelen);
|
|
static int bestups_process_bbb_status_bit(item_t *item, char *value, const size_t valuelen);
|
|
static int bestups_manufacturer(item_t *item, char *value, const size_t valuelen);
|
|
static int bestups_model(item_t *item, char *value, const size_t valuelen);
|
|
static int bestups_batt_runtime(item_t *item, char *value, const size_t valuelen);
|
|
static int bestups_batt_packs(item_t *item, char *value, const size_t valuelen);
|
|
static int bestups_get_pins_shutdown_mode(item_t *item, char *value, const size_t valuelen);
|
|
static int bestups_voltage_settings(item_t *item, char *value, const size_t valuelen);
|
|
|
|
/* ups.conf settings */
|
|
static int pins_shutdown_mode;
|
|
|
|
/* General settings */
|
|
static int inverted_bbb_bit = 0;
|
|
|
|
|
|
/* == Ranges/enums == */
|
|
|
|
/* Range for ups.delay.start */
|
|
static info_rw_t bestups_r_ondelay[] = {
|
|
{ "60", 0 },
|
|
{ "599940", 0 },
|
|
{ "", 0 }
|
|
};
|
|
|
|
/* Range for ups.delay.shutdown */
|
|
static info_rw_t bestups_r_offdelay[] = {
|
|
{ "12", 0 },
|
|
{ "5940", 0 },
|
|
{ "", 0 }
|
|
};
|
|
|
|
/* Range for number of battery packs */
|
|
static info_rw_t bestups_r_batt_packs[] = {
|
|
{ "0", 0 },
|
|
{ "5", 0 },
|
|
{ "", 0 }
|
|
};
|
|
|
|
/* Range for pin shutdown mode */
|
|
static info_rw_t bestups_r_pins_shutdown_mode[] = {
|
|
{ "0", 0 },
|
|
{ "6", 0 },
|
|
{ "", 0 }
|
|
};
|
|
|
|
|
|
/* == qx2nut lookup table == */
|
|
static item_t bestups_qx2nut[] = {
|
|
|
|
/* Query UPS for status
|
|
* > [Q1\r]
|
|
* < [(226.0 195.0 226.0 014 49.0 27.5 30.0 00001000\r]
|
|
* 01234567890123456789012345678901234567890123456
|
|
* 0 1 2 3 4
|
|
*/
|
|
|
|
{ "input.voltage", 0, NULL, "Q1\r", "", 47, '(', "", 1, 5, "%.1f", 0, NULL, NULL, NULL },
|
|
{ "input.voltage.fault", 0, NULL, "Q1\r", "", 47, '(', "", 7, 11, "%.1f", 0, NULL, NULL, NULL },
|
|
{ "output.voltage", 0, NULL, "Q1\r", "", 47, '(', "", 13, 17, "%.1f", 0, NULL, NULL, NULL },
|
|
{ "ups.load", 0, NULL, "Q1\r", "", 47, '(', "", 19, 21, "%.0f", 0, NULL, NULL, NULL },
|
|
{ "input.frequency", 0, NULL, "Q1\r", "", 47, '(', "", 23, 26, "%.1f", 0, NULL, NULL, NULL },
|
|
{ "battery.voltage", 0, NULL, "Q1\r", "", 47, '(', "", 28, 31, "%.2f", 0, NULL, NULL, NULL },
|
|
{ "ups.temperature", 0, NULL, "Q1\r", "", 47, '(', "", 33, 36, "%.1f", 0, NULL, NULL, NULL },
|
|
/* Status bits */
|
|
{ "ups.status", 0, NULL, "Q1\r", "", 47, '(', "", 38, 38, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, blazer_process_status_bits }, /* Utility Fail (Immediate) */
|
|
{ "ups.status", 0, NULL, "Q1\r", "", 47, '(', "", 39, 39, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, blazer_process_status_bits }, /* Battery Low */
|
|
{ "ups.alarm", 0, NULL, "Q1\r", "", 47, '(', "", 41, 41, NULL, 0, NULL, NULL, blazer_process_status_bits }, /* UPS Failed */
|
|
{ "ups.type", 0, NULL, "Q1\r", "", 47, '(', "", 42, 42, "%s", QX_FLAG_STATIC, NULL, NULL, blazer_process_status_bits }, /* UPS Type */
|
|
{ "ups.status", 0, NULL, "Q1\r", "", 47, '(', "", 43, 43, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, blazer_process_status_bits }, /* Test in Progress */
|
|
{ "ups.status", 0, NULL, "Q1\r", "", 47, '(', "", 44, 44, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, blazer_process_status_bits }, /* Shutdown Active */
|
|
/* { "ups.beeper.status", 0, NULL, "Q1\r", "", 47, '(', "", 45, 45, "%s", 0, NULL, NULL, blazer_process_status_bits }, *//* Beeper status: not supported; always 0 */
|
|
{ "ups.status", 0, NULL, "Q1\r", "", 47, '(', "", 40, 40, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, bestups_process_bbb_status_bit }, /* Bypass/Boost or Buck Active - keep this one at the end as it needs the processed data from the previous items */
|
|
|
|
/* Query UPS for ratings and model infos
|
|
* > [ID\r]
|
|
* < [FOR,750,120,120,20.0,27.6\r] case #1: length = 26
|
|
* < [FOR,1500,120,120,20.0,27.6\r] case #2: length = 27
|
|
* < [FOR,3000,120,120,20.0,100.6\r] case #3: length = 28
|
|
* < [FOR, 750,120,120,20.0, 27.6\r] after being preprocessed: length = 28
|
|
* 0123456789012345678901234567
|
|
* 0 1 2
|
|
*/
|
|
|
|
{ "device.mfr", 0, NULL, "ID\r", "", 28, 0, "", 0, 2, "%s", QX_FLAG_STATIC, NULL, bestups_preprocess_id_answer, bestups_manufacturer },
|
|
{ "device.model", 0, NULL, "ID\r", "", 28, 0, "", 0, 2, "%s", QX_FLAG_STATIC, NULL, bestups_preprocess_id_answer, bestups_model },
|
|
{ "ups.power.nominal", 0, NULL, "ID\r", "", 28, 0, "", 4, 7, "%.0f", QX_FLAG_STATIC, NULL, bestups_preprocess_id_answer, NULL },
|
|
{ "input.voltage.nominal", 0, NULL, "ID\r", "", 28, 0, "", 9, 11, "%.0f", QX_FLAG_STATIC, NULL, bestups_preprocess_id_answer, NULL },
|
|
{ "output.voltage.nominal", 0, NULL, "ID\r", "", 28, 0, "", 13, 15, "%.0f", QX_FLAG_STATIC, NULL, bestups_preprocess_id_answer, NULL },
|
|
{ "battery.voltage.low", 0, NULL, "ID\r", "", 28, 0, "", 17, 20, "%.1f", QX_FLAG_SEMI_STATIC, NULL, bestups_preprocess_id_answer, NULL },
|
|
{ "battery.voltage.high", 0, NULL, "ID\r", "", 28, 0, "", 22, 26, "%.1f", QX_FLAG_SEMI_STATIC, NULL, bestups_preprocess_id_answer, NULL },
|
|
|
|
/* Query UPS for battery runtime (not available on the Patriot Pro/Sola 320 model series)
|
|
* > [RT\r]
|
|
* < [025\r]
|
|
* 0123
|
|
* 0
|
|
*/
|
|
|
|
{ "battery.runtime", 0, NULL, "RT\r", "", 4, 0, "", 0, 2, "%.0f", QX_FLAG_SKIP, NULL, NULL, bestups_batt_runtime },
|
|
|
|
/* Query UPS for number of battery packs (available only on the Axxium/Sola 620 model series)
|
|
* > [BP?\r]
|
|
* < [02\r]
|
|
* 012
|
|
* 0
|
|
*/
|
|
|
|
{ "battery.packs", ST_FLAG_RW, bestups_r_batt_packs, "BP?\r", "", 3, 0, "", 0, 1, "%d", QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_SKIP, NULL, NULL, bestups_batt_packs },
|
|
|
|
/* Set number of battery packs to n (integer, 0-5) (available only on the Axxium/Sola 620 model series)
|
|
* > [BPn\r]
|
|
* < []
|
|
*/
|
|
|
|
{ "battery.packs", 0, bestups_r_batt_packs, "BP%.0f\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_SKIP, NULL, NULL, bestups_process_setvar },
|
|
|
|
/* Query UPS for shutdown mode functionality of Pin 1 and Pin 7 on the UPS DB9 communication port (Per Best Power's EPS-0059)
|
|
* > [SS?\r]
|
|
* < [0\r]
|
|
* 01
|
|
* 0
|
|
*/
|
|
|
|
{ "pins_shutdown_mode", ST_FLAG_RW, bestups_r_pins_shutdown_mode, "SS?\r", "", 2, 0, "", 0, 0, "%.0f", QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_NONUT, NULL, NULL, bestups_get_pins_shutdown_mode },
|
|
|
|
/* Set shutdown mode functionality of Pin 1 and Pin 7 on the UPS DB9 communication port (Per Best Power's EPS-0059) to n (integer, 0-6)
|
|
* > [SSn\r]
|
|
* < []
|
|
*/
|
|
|
|
{ "pins_shutdown_mode", 0, bestups_r_pins_shutdown_mode, "SS%.0f\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, NULL, bestups_process_setvar },
|
|
|
|
/* Query UPS for voltage settings
|
|
* > [M\r]
|
|
* < [0\r]
|
|
* 01
|
|
* 0
|
|
*/
|
|
|
|
{ "input.transfer.low", 0, NULL, "M\r", "", 2, 0, "", 0, 0, "%d", 0, NULL, NULL, bestups_voltage_settings },
|
|
{ "input.transfer.boost.low", 0, NULL, "M\r", "", 2, 0, "", 0, 0, "%d", 0, NULL, NULL, bestups_voltage_settings },
|
|
{ "input.transfer.boost.high", 0, NULL, "M\r", "", 2, 0, "", 0, 0, "%d", 0, NULL, NULL, bestups_voltage_settings },
|
|
{ "input.voltage.nominal", 0, NULL, "M\r", "", 2, 0, "", 0, 0, "%d", 0, NULL, NULL, bestups_voltage_settings },
|
|
{ "output.voltage.nominal", 0, NULL, "M\r", "", 2, 0, "", 0, 0, "%d", 0, NULL, NULL, bestups_voltage_settings },
|
|
{ "input.transfer.trim.low", 0, NULL, "M\r", "", 2, 0, "", 0, 0, "%d", 0, NULL, NULL, bestups_voltage_settings },
|
|
{ "input.transfer.trim.high", 0, NULL, "M\r", "", 2, 0, "", 0, 0, "%d", 0, NULL, NULL, bestups_voltage_settings },
|
|
{ "input.transfer.high", 0, NULL, "M\r", "", 2, 0, "", 0, 0, "%d", 0, NULL, NULL, bestups_voltage_settings },
|
|
|
|
/* Instant commands */
|
|
{ "shutdown.return", 0, NULL, "S%s\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, blazer_process_command },
|
|
{ "shutdown.stayoff", 0, NULL, "S%s\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, blazer_process_command },
|
|
{ "shutdown.stop", 0, NULL, "C\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL },
|
|
{ "load.on", 0, NULL, "C\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL },
|
|
{ "load.off", 0, NULL, "S00R0000\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL },
|
|
{ "test.battery.start", 0, NULL, "T%02d\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, blazer_process_command },
|
|
{ "test.battery.start.deep", 0, NULL, "TL\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL },
|
|
{ "test.battery.start.quick", 0, NULL, "T\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL },
|
|
{ "test.battery.stop", 0, NULL, "CT\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL },
|
|
|
|
/* Server-side settable vars */
|
|
{ "ups.delay.start", ST_FLAG_RW, bestups_r_ondelay, NULL, "", 0, 0, "", 0, 0, DEFAULT_ONDELAY, QX_FLAG_ABSENT | QX_FLAG_SETVAR | QX_FLAG_RANGE, NULL, NULL, blazer_process_setvar },
|
|
{ "ups.delay.shutdown", ST_FLAG_RW, bestups_r_offdelay, NULL, "", 0, 0, "", 0, 0, DEFAULT_OFFDELAY, QX_FLAG_ABSENT | QX_FLAG_SETVAR | QX_FLAG_RANGE, NULL, NULL, blazer_process_setvar },
|
|
|
|
/* End of structure. */
|
|
{ NULL, 0, NULL, NULL, "", 0, 0, "", 0, 0, NULL, 0, NULL, NULL, NULL }
|
|
};
|
|
|
|
|
|
/* == Testing table == */
|
|
#ifdef TESTING
|
|
static testing_t bestups_testing[] = {
|
|
{ "Q1\r", "(215.0 195.0 230.0 014 49.0 22.7 30.0 00100000\r", -1 },
|
|
{ "ID\r", "FOR,750,120,120,20.0,27.6\r", -1 },
|
|
{ "RT\r", "015\r", -1 },
|
|
{ "BP?\r", "02\r", -1 },
|
|
{ "BP1\r", "", -1 },
|
|
{ "SS?\r", "0\r", -1 },
|
|
{ "SS2\r", "", -1 },
|
|
{ "M\r", "0\r", -1 },
|
|
{ "S03\r", "", -1 },
|
|
{ "C\r", "", -1 },
|
|
{ "S02R0005\r", "", -1 },
|
|
{ "S.5R0001\r", "", -1 },
|
|
{ "T04\r", "", -1 },
|
|
{ "TL\r", "", -1 },
|
|
{ "T\r", "", -1 },
|
|
{ "CT\r", "", -1 },
|
|
{ NULL }
|
|
};
|
|
#endif /* TESTING */
|
|
|
|
|
|
/* == Support functions == */
|
|
|
|
/* This function allows the subdriver to "claim" a device: return 1 if the device is supported by this subdriver, else 0. */
|
|
static int bestups_claim(void)
|
|
{
|
|
/* We need at least Q1 and ID to run this subdriver */
|
|
|
|
item_t *item = find_nut_info("input.voltage", 0, 0);
|
|
|
|
/* Don't know what happened */
|
|
if (!item)
|
|
return 0;
|
|
|
|
/* No reply/Unable to get value */
|
|
if (qx_process(item, NULL))
|
|
return 0;
|
|
|
|
/* Unable to process value */
|
|
if (ups_infoval_set(item) != 1)
|
|
return 0;
|
|
|
|
/* UPS Model */
|
|
item = find_nut_info("device.model", 0, 0);
|
|
|
|
/* Don't know what happened */
|
|
if (!item) {
|
|
dstate_delinfo("input.voltage");
|
|
return 0;
|
|
}
|
|
|
|
/* No reply/Unable to get value */
|
|
if (qx_process(item, NULL)) {
|
|
dstate_delinfo("input.voltage");
|
|
return 0;
|
|
}
|
|
|
|
/* Unable to process value */
|
|
if (ups_infoval_set(item) != 1) {
|
|
dstate_delinfo("input.voltage");
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Subdriver-specific initups */
|
|
static void bestups_initups(void)
|
|
{
|
|
blazer_initups_light(bestups_qx2nut);
|
|
}
|
|
|
|
/* Subdriver-specific flags/vars */
|
|
static void bestups_makevartable(void)
|
|
{
|
|
addvar(VAR_VALUE, "pins_shutdown_mode", "Set shutdown mode functionality of Pin 1 and Pin 7 on the UPS DB9 communication port (Per Best Power's EPS-0059) to n (integer, 0-6)");
|
|
|
|
blazer_makevartable_light();
|
|
}
|
|
|
|
|
|
/* == Answer preprocess functions == */
|
|
|
|
/* Preprocess the answer we got back from the UPS when queried with 'ID\r': make data begin always at the same indexes */
|
|
static int bestups_preprocess_id_answer(item_t *item, const int len)
|
|
{
|
|
int i;
|
|
char refined[SMALLBUF] = "",
|
|
rawval[SMALLBUF] = "",
|
|
*token,
|
|
*saveptr = NULL;
|
|
|
|
if (len <= 0)
|
|
return len;
|
|
|
|
if (len < 25 || len > 27) {
|
|
upsdebugx(4, "%s: wrong length [%s: %d]", __func__, item->info_type, len);
|
|
return -1;
|
|
}
|
|
|
|
/* e.g.:
|
|
* 1. item->answer = "FOR,750,120,120,20.0,27.6\r"; len = 26
|
|
* 2. item->answer = "FOR,1500,120,120,20.0,27.6\r"; len = 27
|
|
* 3. item->answer = "FOR,3000,120,120,20.0,100.6\r"; len = 28 */
|
|
upsdebugx(4, "read: '%.*s'", (int)strcspn(item->answer, "\r"), item->answer);
|
|
|
|
snprintf(rawval, sizeof(rawval), "%s", item->answer);
|
|
|
|
for (i = 1, token = strtok_r(rawval, ",", &saveptr); token != NULL; i++, token = strtok_r(NULL, ",", &saveptr)) {
|
|
|
|
switch (i)
|
|
{
|
|
case 1:
|
|
snprintf(refined, sizeof(refined), "%s", token);
|
|
continue;
|
|
case 2: /* Output power */
|
|
snprintfcat(refined, sizeof(refined), ",%4s", token);
|
|
continue;
|
|
case 6: /* Battery voltage at full charge (+ trailing CR) */
|
|
snprintfcat(refined, sizeof(refined), ",%6s", token);
|
|
continue;
|
|
default:
|
|
snprintfcat(refined, sizeof(refined), ",%s", token);
|
|
}
|
|
|
|
}
|
|
|
|
if (i != 7 || strlen(refined) != 28) {
|
|
upsdebugx(2, "noncompliant reply: '%.*s'", (int)strcspn(refined, "\r"), refined);
|
|
return -1;
|
|
}
|
|
|
|
upsdebugx(4, "read: '%.*s'", (int)strcspn(refined, "\r"), refined);
|
|
|
|
/* e.g.: item->answer = "FOR, 750,120,120,20.0, 27.6\r"; len = 28 */
|
|
return snprintf(item->answer, sizeof(item->answer), "%s", refined);
|
|
}
|
|
|
|
|
|
/* == Preprocess functions == */
|
|
|
|
/* *SETVAR(/NONUT)* Preprocess setvars */
|
|
static int bestups_process_setvar(item_t *item, char *value, const size_t valuelen)
|
|
{
|
|
if (!strlen(value)) {
|
|
upsdebugx(2, "%s: value not given for %s", __func__, item->info_type);
|
|
return -1;
|
|
}
|
|
|
|
double val = strtod(value, NULL);
|
|
|
|
if (!strcasecmp(item->info_type, "pins_shutdown_mode")) {
|
|
|
|
if (d_equal(val, pins_shutdown_mode)) {
|
|
upslogx(LOG_INFO, "%s is already set to %.0f", item->info_type, val);
|
|
return -1;
|
|
}
|
|
|
|
}
|
|
|
|
#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
|
|
snprintf(value, valuelen, item->command, val);
|
|
#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Bypass/Boost or Buck status */
|
|
static int bestups_process_bbb_status_bit(item_t *item, char *value, const size_t valuelen)
|
|
{
|
|
/* Bypass/Boost/Buck bit is not reliable when a battery test, shutdown or on battery condition occurs: always ignore it in these cases */
|
|
if (!((unsigned int)(qx_status()) & STATUS(OL)) || ((unsigned int)(qx_status()) & (STATUS(CAL) | STATUS(FSD)))) {
|
|
|
|
if (item->value[0] == '1')
|
|
item->value[0] = '0';
|
|
|
|
return blazer_process_status_bits(item, value, valuelen);
|
|
|
|
}
|
|
|
|
/* UPSes with inverted bypass/boost/buck bit */
|
|
if (inverted_bbb_bit) {
|
|
|
|
if (item->value[0] == '1')
|
|
item->value[0] = '0';
|
|
|
|
else if (item->value[0] == '0')
|
|
item->value[0] = '1';
|
|
|
|
}
|
|
|
|
return blazer_process_status_bits(item, value, valuelen);
|
|
}
|
|
|
|
/* Identify UPS manufacturer */
|
|
static int bestups_manufacturer(item_t *item, char *value, const size_t valuelen)
|
|
{
|
|
#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
|
|
|
|
/* Best Power devices */
|
|
if (
|
|
!strcmp(item->value, "AX1") ||
|
|
!strcmp(item->value, "FOR") ||
|
|
!strcmp(item->value, "FTC") ||
|
|
!strcmp(item->value, "PR2") ||
|
|
!strcmp(item->value, "PRO")
|
|
) {
|
|
snprintf(value, valuelen, item->dfl, "Best Power");
|
|
return 0;
|
|
}
|
|
|
|
/* Sola Australia devices */
|
|
if (
|
|
!strcmp(item->value, "325") ||
|
|
!strcmp(item->value, "520") ||
|
|
!strcmp(item->value, "620")
|
|
) {
|
|
snprintf(value, valuelen, item->dfl, "Sola Australia");
|
|
return 0;
|
|
}
|
|
|
|
/* Unknown devices */
|
|
snprintf(value, valuelen, item->dfl, "Unknown");
|
|
|
|
#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Identify UPS model and unskip qx2nut table's items accordingly */
|
|
static int bestups_model(item_t *item, char *value, const size_t valuelen)
|
|
{
|
|
item_t *unskip;
|
|
|
|
#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
|
|
|
|
/* Best Power devices */
|
|
|
|
if (!strcmp(item->value, "AX1")) {
|
|
|
|
snprintf(value, valuelen, item->dfl, "Axxium Rackmount");
|
|
|
|
} else if (!strcmp(item->value, "FOR")) {
|
|
|
|
snprintf(value, valuelen, item->dfl, "Fortress");
|
|
|
|
} else if (!strcmp(item->value, "FTC")) {
|
|
|
|
snprintf(value, valuelen, item->dfl, "Fortress Telecom");
|
|
|
|
} else if (!strcmp(item->value, "PR2")) {
|
|
|
|
snprintf(value, valuelen, item->dfl, "Patriot Pro II");
|
|
inverted_bbb_bit = 1;
|
|
|
|
} else if (!strcmp(item->value, "PRO")) {
|
|
|
|
snprintf(value, valuelen, item->dfl, "Patriot Pro");
|
|
inverted_bbb_bit = 1;
|
|
|
|
/* Sola Australia devices */
|
|
} else if (
|
|
!strcmp(item->value, "320") ||
|
|
!strcmp(item->value, "325") ||
|
|
!strcmp(item->value, "520") ||
|
|
!strcmp(item->value, "525") ||
|
|
!strcmp(item->value, "620")
|
|
) {
|
|
|
|
snprintf(value, valuelen, "Sola %s", item->value);
|
|
|
|
/* Unknown devices */
|
|
} else {
|
|
|
|
snprintf(value, valuelen, item->dfl, "Unknown (%s)", item->value);
|
|
upslogx(LOG_INFO, "Unknown model detected - please report this ID: '%s'", item->value);
|
|
|
|
}
|
|
|
|
/* Unskip qx2nut table's items according to the UPS model */
|
|
|
|
/* battery.runtime var is not available on the Patriot Pro/Sola 320 model series: leave it skipped in these cases, otherwise unskip it */
|
|
if (strcmp(item->value, "PRO") && strcmp(item->value, "320")) {
|
|
|
|
unskip = find_nut_info("battery.runtime", 0, 0);
|
|
|
|
/* Don't know what happened */
|
|
if (!unskip)
|
|
return -1;
|
|
|
|
unskip->qxflags &= ~QX_FLAG_SKIP;
|
|
|
|
}
|
|
|
|
/* battery.packs var is available only on the Axxium/Sola 620 model series: unskip it in these cases */
|
|
if (!strcmp(item->value, "AX1") || !strcmp(item->value, "620")) {
|
|
|
|
unskip = find_nut_info("battery.packs", 0, QX_FLAG_SETVAR);
|
|
|
|
/* Don't know what happened */
|
|
if (!unskip)
|
|
return -1;
|
|
|
|
unskip->qxflags &= ~QX_FLAG_SKIP;
|
|
|
|
}
|
|
|
|
#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Battery runtime */
|
|
static int bestups_batt_runtime(item_t *item, char *value, const size_t valuelen)
|
|
{
|
|
double runtime;
|
|
|
|
if (strspn(item->value, "0123456789 .") != strlen(item->value)) {
|
|
upsdebugx(2, "%s: non numerical value [%s: %s]", __func__, item->info_type, item->value);
|
|
return -1;
|
|
}
|
|
|
|
/* Battery runtime is reported by the UPS in minutes, NUT expects seconds */
|
|
runtime = strtod(item->value, NULL) * 60;
|
|
|
|
#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
|
|
snprintf(value, valuelen, item->dfl, runtime);
|
|
#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Battery packs */
|
|
static int bestups_batt_packs(item_t *item, char *value, const size_t valuelen)
|
|
{
|
|
item_t *unskip;
|
|
|
|
if (strspn(item->value, "0123456789 ") != strlen(item->value)) {
|
|
upsdebugx(2, "%s: non numerical value [%s: %s]", __func__, item->info_type, item->value);
|
|
return -1;
|
|
}
|
|
|
|
#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
|
|
snprintf(value, valuelen, item->dfl, strtol(item->value, NULL, 10));
|
|
#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
|
|
/* Unskip battery.packs setvar */
|
|
unskip = find_nut_info("battery.packs", QX_FLAG_SETVAR, 0);
|
|
|
|
/* Don't know what happened */
|
|
if (!unskip)
|
|
return -1;
|
|
|
|
unskip->qxflags &= ~QX_FLAG_SKIP;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* *NONUT* Get shutdown mode functionality of Pin 1 and Pin 7 on the UPS DB9 communication port (Per Best Power's EPS-0059) as set in the UPS */
|
|
static int bestups_get_pins_shutdown_mode(item_t *item, char *value, const size_t valuelen)
|
|
{
|
|
item_t *unskip;
|
|
long l;
|
|
|
|
if (strspn(item->value, "0123456789") != strlen(item->value)) {
|
|
upsdebugx(2, "%s: non numerical value [%s: %s]", __func__, item->info_type, item->value);
|
|
return -1;
|
|
}
|
|
|
|
l = strtol(item->value, NULL, 10);
|
|
if (l > INT_MAX) {
|
|
upsdebugx(2, "%s: pins_shutdown_mode out of range [%s: %s]",
|
|
__func__, item->info_type, item->value);
|
|
return -1;
|
|
}
|
|
pins_shutdown_mode = (int)l;
|
|
|
|
#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
|
|
snprintf(value, valuelen, item->dfl, pins_shutdown_mode);
|
|
#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
|
|
/* We were not asked by the user to change the value */
|
|
if ((item->qxflags & QX_FLAG_NONUT) && !getval(item->info_type))
|
|
return 0;
|
|
|
|
/* Unskip setvar */
|
|
unskip = find_nut_info(item->info_type, QX_FLAG_SETVAR, 0);
|
|
|
|
/* Don't know what happened */
|
|
if (!unskip)
|
|
return -1;
|
|
|
|
unskip->qxflags &= ~QX_FLAG_SKIP;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Voltage settings */
|
|
static int bestups_voltage_settings(item_t *item, char *value, const size_t valuelen)
|
|
{
|
|
long index;
|
|
int val;
|
|
const char *nominal_voltage;
|
|
const struct {
|
|
const int low; /* Low voltage -> input.transfer.low / input.transfer.boost.low */
|
|
const int boost; /* Boost voltage -> input.transfer.boost.high */
|
|
const int nominal; /* Nominal voltage -> input.voltage.nominal / output.voltage.nominal */
|
|
const int buck; /* Buck voltage -> input.transfer.trim.low */
|
|
const int high; /* High voltage -> input.transfer.high / input.transfer.trim.high */
|
|
} voltage_settings[] = {
|
|
/* U models voltage limits, for:
|
|
* - Fortress (750U, 1050U, 1425U, 1800U and 2250U)
|
|
* - Fortress Rackmount (750, 1050, 1425, 1800, and 2250 VA)
|
|
* - Patriot Pro II (400U, 750U, and 1000U) */
|
|
/* M low boost nominal buck high */
|
|
/* 0 */ { 96, 109, 120, 130, 146 }, /* LEDs lit: 2,3,4 (Default) */
|
|
/* 1 */ { 96, 109, 120, 138, 156 }, /* LEDs lit: 1,3,4 */
|
|
/* 2 */ { 90, 104, 120, 130, 146 }, /* LEDs lit: 2,3,5 */
|
|
/* 3 */ { 90, 104, 120, 138, 156 }, /* LEDs lit: 1,3,5 */
|
|
/* 4 */ { 90, 104, 110, 120, 130 }, /* LEDs lit: 3,4,5 */
|
|
/* 5 */ { 90, 104, 110, 130, 146 }, /* LEDs lit: 2,4,5 */
|
|
/* 6 */ { 90, 96, 110, 120, 130 }, /* LEDs lit: 3,4,6 */
|
|
/* 7 */ { 90, 96, 110, 130, 146 }, /* LEDs lit: 2,4,6 */
|
|
/* 8 */ { 96, 109, 128, 146, 156 }, /* LEDs lit: 1,2,4 */
|
|
/* 9 */ { 90, 104, 128, 146, 156 }, /* LEDs lit: 1,2,5 */
|
|
|
|
/* E models voltage limits, for:
|
|
* - Fortress (750E, 1050E, 1425E, and 2250E)
|
|
* - Fortress Rackmount (750, 1050, 1425, and 2250 VA)
|
|
* - Patriot Pro II (400E, 750E, and 1000E) */
|
|
/* M low boost nominal buck high */
|
|
/* 0 */ { 200, 222, 240, 250, 284 }, /* LEDs lit: 2,3,4 */
|
|
/* 1 */ { 200, 222, 240, 264, 290 }, /* LEDs lit: 1,3,4 */
|
|
/* 2 */ { 188, 210, 240, 250, 284 }, /* LEDs lit: 2,3,5 */
|
|
/* 3 */ { 188, 210, 240, 264, 290 }, /* LEDs lit: 1,3,5 */
|
|
/* 4 */ { 188, 210, 230, 244, 270 }, /* LEDs lit: 3,4,5 (Default) */
|
|
/* 5 */ { 188, 210, 230, 250, 284 }, /* LEDs lit: 2,4,5 */
|
|
/* 6 */ { 180, 200, 230, 244, 270 }, /* LEDs lit: 3,4,6 */
|
|
/* 7 */ { 180, 200, 230, 250, 284 }, /* LEDs lit: 2,4,6 */
|
|
/* 8 */ { 165, 188, 208, 222, 244 }, /* LEDs lit: 4,5,6 */
|
|
/* 9 */ { 165, 188, 208, 244, 270 } /* LEDs lit: 3,5,6 */
|
|
};
|
|
|
|
if (strspn(item->value, "0123456789") != strlen(item->value)) {
|
|
upsdebugx(2, "%s: non numerical value [%s: %s]", __func__, item->info_type, item->value);
|
|
return -1;
|
|
}
|
|
|
|
index = strtol(item->value, NULL, 10);
|
|
|
|
if (index < 0 || index > 9) {
|
|
upsdebugx(2, "%s: value '%ld' out of range [0..9]", __func__, index);
|
|
return -1;
|
|
}
|
|
|
|
nominal_voltage = dstate_getinfo("input.voltage.nominal");
|
|
|
|
if (!nominal_voltage)
|
|
nominal_voltage = dstate_getinfo("output.voltage.nominal");
|
|
|
|
if (!nominal_voltage) {
|
|
upsdebugx(2, "%s: unable to get nominal voltage", __func__);
|
|
return -1;
|
|
}
|
|
|
|
/* E models */
|
|
if (strtol(nominal_voltage, NULL, 10) > 160)
|
|
index += 10;
|
|
|
|
if (!strcasecmp(item->info_type, "input.transfer.low") || !strcasecmp(item->info_type, "input.transfer.boost.low")) {
|
|
|
|
val = voltage_settings[index].low;
|
|
|
|
} else if (!strcasecmp(item->info_type, "input.transfer.boost.high")) {
|
|
|
|
val = voltage_settings[index].boost;
|
|
|
|
} else if (!strcasecmp(item->info_type, "input.voltage.nominal") || !strcasecmp(item->info_type, "output.voltage.nominal")) {
|
|
|
|
val = voltage_settings[index].nominal;
|
|
|
|
} else if (!strcasecmp(item->info_type, "input.transfer.trim.low")) {
|
|
|
|
val = voltage_settings[index].buck;
|
|
|
|
} else if (!strcasecmp(item->info_type, "input.transfer.trim.high") || !strcasecmp(item->info_type, "input.transfer.high")) {
|
|
|
|
val = voltage_settings[index].high;
|
|
|
|
} else {
|
|
|
|
/* Don't know what happened */
|
|
return -1;
|
|
|
|
}
|
|
|
|
#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
|
|
snprintf(value, valuelen, item->dfl, val);
|
|
#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* == Subdriver interface == */
|
|
subdriver_t bestups_subdriver = {
|
|
BESTUPS_VERSION,
|
|
bestups_claim,
|
|
bestups_qx2nut,
|
|
bestups_initups,
|
|
NULL,
|
|
bestups_makevartable,
|
|
NULL,
|
|
NULL,
|
|
#ifdef TESTING
|
|
bestups_testing,
|
|
#endif /* TESTING */
|
|
};
|