nut/drivers/nutdrv_qx_ablerex.c

441 lines
15 KiB
C
Raw Normal View History

2022-06-29 10:37:36 +00:00
/* nutdrv_qx_ablerex.c - Subdriver for Ablerex Qx protocol based UPSes
*
* Copyright (C)
* 2013 Daniele Pezzini <hyouko@gmail.com>
* 2021 Ablerex Software <Ablerex.software@ablerex.com.tw>
*
* 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 "nutdrv_qx.h"
#include "nutdrv_qx_blazer-common.h"
#include "nutdrv_qx_ablerex.h"
#define ABLEREX_VERSION "Ablerex 0.01"
static int Q5_Vbc = -1;
static int ablerexQ5Vb = -1;
static int ablerex_Q5(item_t *item, char *value, const size_t valuelen) {
/*
int RawValue = 0;
RawValue = (unsigned char)item->value[0] * 256 + (unsigned char)item->value[1];
*/
//ablerexQ5Vb = (unsigned char)buf[7] * 256 + (unsigned char)buf[8];
//Q5_Vbc = (unsigned char)buf[9] * 256 + (unsigned char)buf[10];
upsdebugx(2, "Q51: %d %d %d %d %d %d", item->answer[0], item->answer[1], item->answer[2], item->answer[3], item->answer[4], item->answer[5]);
upsdebugx(2, "Q52: %d %d %d %d %d %d", item->answer[6], item->answer[7], item->answer[8], item->answer[9], item->answer[10], item->answer[11]);
upsdebugx(2, "Q53: %d %d %d %d", item->answer[12], item->answer[13], item->answer[14], item->answer[15]);
int Q5_Fout = (unsigned char)item->answer[1] * 256 + (unsigned char)item->answer[2];
int Q5_Vb = (unsigned char)item->answer[7] * 256 + (unsigned char)item->answer[8];
Q5_Vbc = (unsigned char)item->answer[9] * 256 + (unsigned char)item->answer[10];
//int Q5_InvW = (unsigned char)item->answer[11] * 256 + (unsigned char)item->answer[12];
int Q5_Err = (unsigned char)item->answer[13] * 256 + (unsigned char)item->answer[14];
int Q5_O_Cur = (unsigned char)item->answer[15] * 256 + (unsigned char)item->answer[16];
ablerexQ5Vb = Q5_Vb;
upsdebugx(2, "Q5: %.1f %d %.1f", 0.1 * Q5_Fout, Q5_Err, 0.1 * Q5_O_Cur);
upsdebugx(2, "Q5Vb: %d Vbc %d", Q5_Vb, Q5_Vbc);
dstate_setinfo("output.frequency", "%.1f", 0.1 * Q5_Fout);
dstate_setinfo("ups.alarm", "%d", Q5_Err);
dstate_setinfo("output.current", "%.1f", 0.1 * Q5_O_Cur);
snprintf(value, valuelen, "%.1f", Q5_Fout * 0.1);
/*
switch (item->from)
{
case 1:
snprintf(value, valuelen, "%.1f", RawValue * 0.1);
upsdebugx(2, "Q51: %.1f", 0.1*RawValue);
break;
case 13:
snprintf(value, valuelen, "%.0f", RawValue);
upsdebugx(2, "Q52: %.0f", 0.1*RawValue);
break;
case 15:
snprintf(value, valuelen, "%.1f", RawValue * 0.1);
upsdebugx(2, "Q53: %.1f", 0.1*RawValue);
break;
default:
//Don't know what happened
return -1;
}
*/
return 0;
}
static int ablerex_battery(item_t *item, char *value, const size_t valuelen) {
double BattV = 0.0;
BattV = strtod(item->value, NULL);
upsdebugx(2, "battvoltact2: %.2f", BattV);
if (!dstate_getinfo("battery.voltage.nominal"))
{
snprintf(value, valuelen, "%.2f", BattV);
return 0;
}
double nomBattV = 0.0;
nomBattV = strtod(dstate_getinfo("battery.voltage.nominal"), NULL);
upsdebugx(2, "battvoltact1: %.2f", nomBattV);
//return 0;
double battvoltact = 0.0;
if (ablerexQ5Vb > 0) {
battvoltact = ablerexQ5Vb * nomBattV / 1200;
} else {
if (BattV > 3.0) {
battvoltact = BattV;
} else {
battvoltact = BattV * 6 * nomBattV / 12;
}
}
snprintf(value, valuelen, "%.2f", battvoltact);
upsdebugx(2, "battvoltact: %.2f / %.2f", battvoltact, BattV);
return 0;
}
static int ablerex_battery_charge(double BattIn)
{
const double onlineP[] = {
2.22, 2.21, 2.20, 2.19, 2.18, 2.17, 2.16, 2.15, 2.14, 2.13,
2.12, 2.11, 2.10, 2.09, 2.08, 2.07, 2.06, 2.05, 2.04, 2.03,
2.02, 2.01, 2.00, 1.99, 1.98, 1.97, 1.96, 1.95, 1.94, 1.93,
1.92, 1.91, 1.90, 1.89, 1.88, 1.87, 1.86, 1.85, 1.84, 1.83,
1.82, 1.81, 1.80, 1.79, 1.78, 1.77, 1.76, 1.75, 1.74, 1.73,
1.72, 1.71, 1.70, 1.69, 1.68, 1.67
};
const int onlineC[] = {
100, 90, 88, 87, 85, 83, 82, 80, 78, 77,
75, 73, 72, 70, 68, 65, 65, 62, 62, 58,
58, 55, 55, 53, 52, 50, 48, 47, 45, 43,
42, 40, 38, 37, 35, 33, 32, 30, 28, 27,
25, 23, 22, 20, 18, 17, 15, 13, 12, 10,
8, 7, 5, 3, 2, 0, -1
};
const double offlineP[] = {
13.5, 13.3, 13.2, 13.1, 13, 12.9, 12.8, 12.7, 12.6, 12.5,
12.4, 12.3, 12.2, 12.1, 12, 11.9, 11.8, 11.7, 11.6, 11.5,
11.4, 11.3, 11.2, 11.1, 11, 10.9, 10.8, 10.7, 10.6, 10.5,
10.4, 10.3, 10.2, 10.1, 10
};
const int offlineC[] = {
100, 90, 88, 86, 83, 80, 77, 74, 72, 69,
66, 63, 61, 58, 55, 52, 49, 47, 44, 41,
38, 36, 33, 30, 27, 24, 22, 19, 16, 13,
11, 8, 5, 2, 0, -1
};
int charge = 0;
int i;
if (BattIn < 3.0) {
for (i = 0; onlineC[i] > 0; i++) {
if (BattIn >= onlineP[i]) {
charge = onlineC[i];
break;
}
}
} else {
//double nomBattV = strtod(dstate_getinfo("battery.voltage.nominal"), NULL);
//double battV = BattIn / (nomBattV / 12);
for (i = 0; offlineC[i] > 0; i++) {
if (BattIn >= offlineP[i]) {
charge = offlineC[i];
break;
}
}
}
return charge;
}
static int ablerex_batterycharge(item_t *item, char *value, const size_t valuelen) {
double BattV = 0.0;
BattV = strtod(item->value, NULL);
upsdebugx(2, "battvoltc2: %.2f", BattV);
if (!dstate_getinfo("battery.voltage.nominal"))
{
snprintf(value, valuelen, "%d", 100);
return 0;
}
double nomBattV = 0.0;
nomBattV = strtod(dstate_getinfo("battery.voltage.nominal"), NULL);
upsdebugx(2, "battvv1: %.2f", nomBattV);
//return 0;
if (BattV > 3.0) {
BattV = BattV / (nomBattV / 12);
}
int BattP = ablerex_battery_charge(BattV);
//dstate_setinfo("battery.charge", "%.0f", BattP);
snprintf(value, valuelen, "%d", BattP);
upsdebugx(2, "battcharge: %d", BattP);
return 0;
}
static int ablerex_initbattery(item_t *item, char *value, const size_t valuelen) {
double nomBattV = strtod(dstate_getinfo("battery.voltage.nominal"), NULL);
double batthigh = 0.0;
double battlow = 0.0;
if (Q5_Vbc > 0) {
battlow = Q5_Vbc * nomBattV / 1200;
} else {
battlow = 960 * nomBattV / 1200;
}
batthigh = 1365 * nomBattV / 1200;
switch (item->from)
{
case 1:
snprintf(value, valuelen, "%.2f", battlow);
upsdebugx(2, "BattLow: %.2f", battlow);
break;
case 2:
snprintf(value, valuelen, "%.2f", batthigh);
upsdebugx(2, "BattHigh: %.2f", batthigh);
break;
default:
/* Don't know what happened */
return -1;
}
return 0;
}
static int ablerex_At(item_t *item, char *value, const size_t valuelen) {
int RawValue = 0;
RawValue = (unsigned char)item->answer[1] * 65536 * 256 + (unsigned char)item->answer[2] * 65536
+ (unsigned char)item->answer[3] * 256 + (unsigned char)item->answer[4];
snprintf(value, valuelen, "%d", RawValue);
upsdebugx(2, "At: %d", RawValue);
return 0;
}
static int ablerex_TR(item_t *item, char *value, const size_t valuelen) {
char TR[8];
TR[0] = item->answer[1];
TR[1] = item->answer[2];
TR[2] = item->answer[3];
TR[3] = item->answer[4];
TR[4] = 0;
snprintf(value, valuelen, "%s", TR);
upsdebugx(2, "At: %s", TR);
return 0;
}
static int ablerex_process_status_bits(item_t *item, char *value, const size_t valuelen)
{
char *val = "";
switch (item->from)
{
case 40: /* Bypass/Boost or Buck Active */
if (item->value[0] == '1') {
double vi, vo;
vi = strtod(dstate_getinfo("input.voltage"), NULL);
vo = strtod(dstate_getinfo("output.voltage"), NULL);
if (item->value[2] == '1') {/* UPS Type is Standby (0 is On_line) */
if (vo < 0.5 * vi) {
upsdebugx(2, "%s: output voltage too low", __func__);
return -1;
} else if (vo < 0.95 * vi) {
status_set("TRIM");
} else if (vo < 1.05 * vi) {
status_set("BYPASS");
} else if (vo < 1.5 * vi) {
status_set("BOOST");
} else {
upsdebugx(2, "%s: output voltage too high", __func__);
return -1;
}
} else {
status_set("BYPASS");
}
}
break;
case 41: /* UPS Failed - ups.alarm */
if (item->value[0] == '1') { /* Battery abnormal */
status_set("RB");
}
double vout = strtod(dstate_getinfo("output.voltage"), NULL);
if (vout < 50.0) {
status_set("OFF");
}
break;
default:
/* Don't know what happened */
return -1;
}
snprintf(value, valuelen, "%s", val);
return 0;
}
/* qx2nut lookup table */
static item_t ablerex_qx2nut[] = {
/*
* > [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, ablerex_battery },
{ "battery.charge", 0, NULL, "Q1\r", "", 47, '(', "", 28, 31, "%.2f", 0, NULL, NULL, ablerex_batterycharge },
{ "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.status", 0, NULL, "Q1\r", "", 47, '(', "", 40, 42, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, ablerex_process_status_bits }, /* Ablerex Bypass/Boost or Buck Active */
{ "ups.status", 0, NULL, "Q1\r", "", 47, '(', "", 41, 41, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, ablerex_process_status_bits }, /* Ablerex UPS Failed */
{ "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 */
/*
* > [F\r]
* < [#220.0 000 024.0 50.0\r]
* 0123456789012345678901
* 0 1 2
*/
{ "output.voltage.nominal", 0, NULL, "F\r", "", 22, '#', "", 1, 5, "%.1f", QX_FLAG_QUICK_POLL, NULL, NULL, NULL },
{ "output.current.nominal", 0, NULL, "F\r", "", 22, '#', "", 7, 9, "%.1f", QX_FLAG_QUICK_POLL, NULL, NULL, NULL },
{ "battery.voltage.nominal", 0, NULL, "F\r", "", 22, '#', "", 11, 15, "%.1f", QX_FLAG_QUICK_POLL, NULL, NULL, NULL },
{ "output.frequency.nominal", 0, NULL, "F\r", "", 22, '#', "", 17, 20, "%.1f", QX_FLAG_QUICK_POLL, NULL, NULL, NULL },
{ "battery.voltage.low", 0, NULL, "F\r", "", 22, '#', "", 1, 2, "%.2f", QX_FLAG_QUICK_POLL, NULL, NULL, ablerex_initbattery },
{ "battery.voltage.high", 0, NULL, "F\r", "", 22, '#', "", 2, 3, "%.2f", QX_FLAG_QUICK_POLL, NULL, NULL, ablerex_initbattery },
/* Ablerex */
{ "output.frequency", 0, NULL, "Q5\r", "", 22, '(', "", 1, 18, "%.1f", 0, NULL, NULL, ablerex_Q5 },
{ "battery.runtime", 0, NULL, "At\r", "", 0, '(', "", 0, 0, "%d", 0, NULL, NULL, ablerex_At },
//{ "ups.alarm", 0, NULL, "Q5\r", "", 22, '(', "", 1, 14, "%.0f", 0, QX_FLAG_QUICK_POLL, NULL, ablerex_Q5 },
{ "ups.test.result", 0, NULL, "TR\r", "", 0, '#', "", 0, 0, "%s", 0, NULL, NULL, ablerex_TR },
//{ "output.current", 0, NULL, "Q5\r", "", 22, '(', "", 1, 16, "%.1f", 0, QX_FLAG_QUICK_POLL, NULL, ablerex_Q5 },
/*
* > [I\r]
* < [#------------- ------ VT12046Q \r]
* 012345678901234567890123456789012345678
* 0 1 2 3
*/
{ "device.mfr", 0, NULL, "I\r", "", 39, '#', "", 1, 15, "%s", QX_FLAG_STATIC | QX_FLAG_TRIM, NULL, NULL, NULL },
{ "device.model", 0, NULL, "I\r", "", 39, '#', "", 17, 26, "%s", QX_FLAG_STATIC | QX_FLAG_TRIM, NULL, NULL, NULL },
{ "ups.firmware", 0, NULL, "I\r", "", 39, '#', "", 28, 37, "%s", QX_FLAG_STATIC | QX_FLAG_TRIM, NULL, NULL, NULL },
/* Instant commands */
{ "beeper.toggle", 0, NULL, "Q\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL },
{ "load.off", 0, NULL, "S.2\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 },
{ "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%sR0000\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 },
{ "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, blazer_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, blazer_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 ablerex_testing[] = {
{ "Q1\r", "(215.0 195.0 230.0 014 49.0 22.7 30.0 00000000\r", -1 },
{ "F\r", "#230.0 000 024.0 50.0\r", -1 },
{ "I\r", "#NOT_A_LIVE_UPS TESTING TESTING \r", -1 },
{ "Q\r", "", -1 },
{ "S03\r", "", -1 },
{ "C\r", "", -1 },
{ "S02R0005\r", "", -1 },
{ "S.5R0000\r", "", -1 },
{ "T04\r", "", -1 },
{ "TL\r", "", -1 },
{ "T\r", "", -1 },
{ "CT\r", "", -1 },
{ NULL }
};
#endif /* TESTING */
/* Subdriver-specific initups */
static void ablerex_initups(void)
{
blazer_initups(ablerex_qx2nut);
}
/* Subdriver interface */
subdriver_t ablerex_subdriver = {
ABLEREX_VERSION,
blazer_claim,
ablerex_qx2nut,
ablerex_initups,
NULL,
blazer_makevartable,
"ACK",
NULL,
#ifdef TESTING
ablerex_testing,
#endif /* TESTING */
};