/* nutdrv_qx_ablerex.c - Subdriver for Ablerex Qx protocol based UPSes * * Copyright (C) * 2013 Daniele Pezzini * 2021 Ablerex Software * * 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 */ };