/* nutdrv_qx_voltronic.c - Subdriver for Voltronic Power UPSes * * Copyright (C) * 2013 Daniele Pezzini * * 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_voltronic.h" #define VOLTRONIC_VERSION "Voltronic 0.03" /* Support functions */ static int voltronic_claim(void); static void voltronic_makevartable(void); static void voltronic_massive_unskip(const int protocol); /* Range/enum functions */ static int voltronic_batt_low(char *value, size_t len); static int voltronic_bypass_volt_max(char *value, size_t len); static int voltronic_bypass_volt_min(char *value, size_t len); static int voltronic_bypass_freq_max(char *value, size_t len); static int voltronic_bypass_freq_min(char *value, size_t len); static int voltronic_eco_freq_min(char *value, size_t len); static int voltronic_eco_freq_max(char *value, size_t len); /* Preprocess functions */ static int voltronic_process_setvar(item_t *item, char *value, size_t valuelen); static int voltronic_process_command(item_t *item, char *value, size_t valuelen); static int voltronic_capability(item_t *item, char *value, size_t valuelen); static int voltronic_capability_set(item_t *item, char *value, size_t valuelen); static int voltronic_capability_set_nonut(item_t *item, char *value, size_t valuelen); static int voltronic_capability_reset(item_t *item, char *value, size_t valuelen); static int voltronic_eco_volt(item_t *item, char *value, size_t valuelen); static int voltronic_eco_volt_range(item_t *item, char *value, size_t valuelen); static int voltronic_eco_freq(item_t *item, char *value, size_t valuelen); static int voltronic_bypass(item_t *item, char *value, size_t valuelen); static int voltronic_batt_numb(item_t *item, char *value, size_t valuelen); static int voltronic_batt_runtime(item_t *item, char *value, size_t valuelen); static int voltronic_protocol(item_t *item, char *value, size_t valuelen); static int voltronic_fault(item_t *item, char *value, size_t valuelen); static int voltronic_warning(item_t *item, char *value, size_t valuelen); static int voltronic_mode(item_t *item, char *value, size_t valuelen); static int voltronic_status(item_t *item, char *value, size_t valuelen); static int voltronic_output_powerfactor(item_t *item, char *value, size_t valuelen); static int voltronic_serial_numb(item_t *item, char *value, size_t valuelen); static int voltronic_outlet(item_t *item, char *value, size_t valuelen); static int voltronic_outlet_delay(item_t *item, char *value, size_t valuelen); static int voltronic_outlet_delay_set(item_t *item, char *value, size_t valuelen); static int voltronic_p31b(item_t *item, char *value, size_t valuelen); static int voltronic_p31b_set(item_t *item, char *value, size_t valuelen); static int voltronic_p31g(item_t *item, char *value, size_t valuelen); static int voltronic_p31g_set(item_t *item, char *value, size_t valuelen); static int voltronic_phase(item_t *item, char *value, size_t valuelen); static int voltronic_phase_set(item_t *item, char *value, size_t valuelen); static int voltronic_parallel(item_t *item, char *value, size_t valuelen); /* Capability vars */ static char *bypass_alarm, *battery_alarm, *bypass_when_off, *alarm_control, *converter_mode, *eco_mode, *battery_open_status_check, *bypass_forbidding, *site_fault_detection, *advanced_eco_mode, *constant_phase_angle, *limited_runtime_on_battery; /* ups.conf settings */ static int max_bypass_volt, min_bypass_volt, battery_number, output_phase_angle, work_range_type; static double max_bypass_freq, min_bypass_freq; /* == Ranges/enums == */ /* Range for ups.delay.start */ static info_rw_t voltronic_r_ondelay[] = { { "0", 0 }, { "599940", 0 }, { "", 0 } }; /* Range for ups.delay.shutdown */ static info_rw_t voltronic_r_offdelay[] = { { "12", 0 }, { "5940", 0 }, { "", 0 } }; /* Enumlist for output phase angle */ static info_rw_t voltronic_e_phase[] = { { "000", 0 }, { "120", 0 }, { "180", 0 }, { "240", 0 }, { "", 0 } }; /* Range for battery low voltage */ static info_rw_t voltronic_r_batt_low[] = { { "20", 0 }, { "24", voltronic_batt_low }, { "28", voltronic_batt_low }, { "", 0 } }; /* Preprocess range value for battery low voltage */ static int voltronic_batt_low(char *value, size_t len) { int val = strtol(value, NULL, 10); const char *ovn = dstate_getinfo("output.voltage.nominal"), *ocn = dstate_getinfo("output.current.nominal"); if (!ovn || !ocn) { upsdebugx(2, "%s: unable to get the value of output voltage nominal/output current nominal", __func__); return -1; } if ((strtol(ovn, NULL, 10) * strtol(ocn, NULL, 10)) < 1000) { if (val == 24) return 0; else return -1; } else { if (val == 28) return 0; else return -1; } } /* Range for outlet.n.delay.shutdown */ static info_rw_t voltronic_r_outlet_delay[] = { { "0", 0 }, { "59940", 0 }, { "", 0 } }; /* Enumlist for device grid working range type */ static info_rw_t voltronic_e_work_range[] = { { "Appliance", 0 }, /* 00 */ { "UPS", 0 }, /* 01 */ { "", 0 } }; /* Enumlist for battery type */ static info_rw_t voltronic_e_batt_type[] = { { "Li", 0 }, /* 00 */ { "Flooded", 0 }, /* 01 */ { "AGM", 0 }, /* 02 */ { "", 0 } }; /* Range for number of battery packs */ static info_rw_t voltronic_r_batt_packs[] = { { "1", 0 }, { "99", 0 }, { "", 0 } }; /* Range for number of batteries */ static info_rw_t voltronic_r_batt_numb[] = { { "1", 0 }, { "9", 0 }, { "", 0 } }; /* Range for Bypass Mode maximum voltage */ static info_rw_t voltronic_r_bypass_volt_max[] = { { "60", voltronic_bypass_volt_max }, /* P09 */ { "115", voltronic_bypass_volt_max }, /* P02/P03/P10/P13/P14/P99 ivn<200 */ { "120", voltronic_bypass_volt_max }, /* P01 ivn<200 */ { "132", voltronic_bypass_volt_max }, /* P99 ivn<200 */ { "138", voltronic_bypass_volt_max }, /* P02/P03/P10/P13/P14 ivn<200 */ { "140", voltronic_bypass_volt_max }, /* P01 ivn<200, P09 */ { "230", voltronic_bypass_volt_max }, /* P01 ivn>=200 */ { "231", voltronic_bypass_volt_max }, /* P02/P03/P10/P13/P14/P99 ivn>=200 */ { "261", voltronic_bypass_volt_max }, /* P99 ivn>=200 */ { "264", voltronic_bypass_volt_max }, /* P01 ivn>=200 */ { "276", voltronic_bypass_volt_max }, /* P02/P03/P10/P13/P14 ivn>=200 */ { "", 0 } }; /* Preprocess range value for Bypass Mode maximum voltage */ static int voltronic_bypass_volt_max(char *value, size_t len) { int protocol = strtol(dstate_getinfo("ups.firmware.aux")+1, NULL, 10), val = strtol(value, NULL, 10), ivn; const char *involtnom = dstate_getinfo("input.voltage.nominal"); if (!involtnom) { upsdebugx(2, "%s: unable to get input.voltage.nominal", __func__); return -1; } ivn = strtol(involtnom, NULL, 10); switch (val) { case 60: /* P09 */ if (protocol == 9) return 0; break; case 115: /* P02/P03/P10/P13/P14/P99 ivn<200 */ if (ivn >= 200) return -1; if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99) return 0; break; case 120: /* P01 ivn<200 */ if (ivn >= 200) return -1; if (protocol == 1) return 0; break; case 132: /* P99 ivn<200 */ if (ivn >= 200) return -1; if (protocol == 99) return 0; break; case 138: /* P02/P03/P10/P13/P14 ivn<200 */ if (ivn >= 200) return -1; if (protocol == 2 || protocol == 2 || protocol == 10 || protocol == 13 || protocol == 14) return 0; break; case 140: /* P01 ivn<200, P09 */ if (protocol == 9) return 0; if (ivn >= 200) return -1; if (protocol == 1) return 0; break; case 230: /* P01 ivn>=200 */ if (ivn < 200) return -1; if (protocol == 1) return 0; break; case 231: /* P02/P03/P10/P13/P14/P99 ivn>=200 */ if (ivn < 200) return -1; if (protocol == 2 || protocol == 2 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99) return 0; break; case 261: /* P99 ivn>=200 */ if (ivn < 200) return -1; if (protocol == 99) return 0; break; case 264: /* P01 ivn>=200 */ if (ivn < 200) return -1; if (protocol == 1) return 0; break; case 276: /* P02/P03/P10/P13/P14 ivn>=200 */ if (ivn < 200) return -1; if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14) return 0; break; default: upsdebugx(2, "%s: unknown value (%s)", __func__, value); break; } return -1; } /* Range for Bypass Mode minimum voltage */ static info_rw_t voltronic_r_bypass_volt_min[] = { { "50", voltronic_bypass_volt_min }, /* P99 ivn<200 */ { "55", voltronic_bypass_volt_min }, /* P02/P03/P10/P13/P14 ivn<200 */ { "60", voltronic_bypass_volt_min }, /* P09 */ { "85", voltronic_bypass_volt_min }, /* P01/P99 ivn<200 */ { "104", voltronic_bypass_volt_min }, /* P02/P03/P10/P13/P14 ivn<200 */ { "110", voltronic_bypass_volt_min }, /* P02/P03/P10/P13/P14 ivn>=200 */ { "115", voltronic_bypass_volt_min }, /* P01 ivn<200 */ { "140", voltronic_bypass_volt_min }, /* P09 */ { "149", voltronic_bypass_volt_min }, /* P99 ivn>=200 */ { "170", voltronic_bypass_volt_min }, /* P01 ivn>=200 */ { "209", voltronic_bypass_volt_min }, /* P02/P03/P10/P13/P14/P99 ivn>=200 */ { "220", voltronic_bypass_volt_min }, /* P01 ivn>=200 */ { "", 0 } }; /* Preprocess range value for Bypass Mode minimum voltage */ static int voltronic_bypass_volt_min(char *value, size_t len) { int protocol = strtol(dstate_getinfo("ups.firmware.aux")+1, NULL, 10), val = strtol(value, NULL, 10), ivn; const char *involtnom = dstate_getinfo("input.voltage.nominal"); if (!involtnom) { upsdebugx(2, "%s: unable to get input.voltage.nominal", __func__); return -1; } ivn = strtol(involtnom, NULL, 10); switch (val) { case 50: /* P99 ivn<200 */ if (ivn >= 200) return -1; if (protocol == 99) return 0; break; case 55: /* P02/P03/P10/P13/P14 ivn<200 */ if (ivn >= 200) return -1; if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 10 || protocol == 13 || protocol == 14) return 0; break; case 60: /* P09 */ case 140: /* P09 */ if (protocol == 9) return 0; break; case 85: /* P01/P99 ivn<200 */ if (ivn >= 200) return -1; if (protocol == 1 || protocol == 99) return 0; break; case 104: /* P02/P03/P10/P13/P14 ivn<200 */ if (ivn >= 200) return -1; if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14) return 0; break; case 110: /* P02/P03/P10/P13/P14 ivn>=200 */ if (ivn < 200) return -1; if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14) return 0; break; case 115: /* P01 ivn<200 */ if (ivn >= 200) return -1; if (protocol == 1) return 0; break; case 149: /* P99 ivn>=200 */ if (ivn < 200) return -1; if (protocol == 99) return 0; break; case 170: /* P01 ivn>=200 */ if (ivn < 200) return -1; if (protocol == 1) return 0; break; case 209: /* P02/P03/P10/P13/P14/P99 ivn>=200 */ if (ivn < 200) return -1; if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99) return 0; break; case 220: /* P01 ivn>=200 */ if (ivn < 200) return -1; if (protocol == 1) return 0; break; default: upsdebugx(2, "%s: unknown value (%s)", __func__, value); break; } return -1; } /* Range for Bypass Mode maximum frequency */ static info_rw_t voltronic_r_bypass_freq_max[] = { { "51.0", voltronic_bypass_freq_max }, /* P01/P09/P02/P03/P10/P13/P14/P99 ofn==50.0 */ { "54.0", voltronic_bypass_freq_max }, /* P02/P03/P10/P13/P14/P99 ofn==50.0 */ { "60.0", voltronic_bypass_freq_max }, /* P01/P09 ofn==50.0 */ { "61.0", voltronic_bypass_freq_max }, /* P01/P09/P02/P03/P10/P13/P14/P99 ofn==60.0 */ { "64.0", voltronic_bypass_freq_max }, /* P02/P03/P10/P13/P14/P99 ofn==60.0 */ { "70.0", voltronic_bypass_freq_max }, /* P01/P09 ofn==60.0 */ { "", 0 } }; /* Preprocess range value for Bypass Mode maximum frequency */ static int voltronic_bypass_freq_max(char *value, size_t len) { int protocol = strtol(dstate_getinfo("ups.firmware.aux")+1, NULL, 10), val = strtol(value, NULL, 10); double ofn; const char *outfreqnom = dstate_getinfo("output.frequency.nominal"); if (!outfreqnom) { upsdebugx(2, "%s: unable to get output.frequency.nominal", __func__); return -1; } ofn = strtod(outfreqnom, NULL); switch (val) { case 51: /* P01/P09/P02/P03/P10/P13/P14/P99 ofn==50.0 */ if (ofn != 50.0) return -1; if (protocol == 1 || protocol == 2 || protocol == 3 || protocol == 9 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99) return 0; break; case 54: /* P02/P03/P10/P13/P14/P99 ofn==50.0 */ if (ofn != 50.0) return -1; if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99) return 0; break; case 60: /* P01/P09 ofn==50.0 */ if (ofn != 50.0) return -1; if (protocol == 1 || protocol == 9) return 0; break; case 61: /* P01/P09/P02/P03/P10/P13/P14/P99 ofn==60.0 */ if (ofn != 60.0) return -1; if (protocol == 1 || protocol == 2 || protocol == 3 || protocol == 9 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99) return 0; break; case 64: /* P02/P03/P10/P13/P14/P99 ofn==60.0 */ if (ofn != 60.0) return -1; if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99) return 0; break; case 70: /* P01/P09 ofn==60.0 */ if (ofn != 60.0) return -1; if (protocol == 1 || protocol == 9) return 0; break; default: upsdebugx(2, "%s: unknown value (%s)", __func__, value); break; } return -1; } /* Range for Bypass Mode minimum frequency */ static info_rw_t voltronic_r_bypass_freq_min[] = { { "40.0", voltronic_bypass_freq_min }, /* P01/P09 ofn==50.0 */ { "46.0", voltronic_bypass_freq_min }, /* P02/P03/P10/P13/P14/P99 ofn==50.0 */ { "49.0", voltronic_bypass_freq_min }, /* P01/P09/P02/P03/P10/P13/P14/P99 ofn==50.0 */ { "50.0", voltronic_bypass_freq_min }, /* P01/P09 ofn==60.0 */ { "56.0", voltronic_bypass_freq_min }, /* P02/P03/P10/P13/P14/P99 ofn==60.0 */ { "59.0", voltronic_bypass_freq_min }, /* P01/P09/P02/P03/P10/P13/P14/P99 ofn==60.0 */ { "", 0 } }; /* Preprocess range value for Bypass Mode minimum frequency */ static int voltronic_bypass_freq_min(char *value, size_t len) { int protocol = strtol(dstate_getinfo("ups.firmware.aux")+1, NULL, 10), val = strtol(value, NULL, 10); double ofn; const char *outfreqnom = dstate_getinfo("output.frequency.nominal"); if (!outfreqnom) { upsdebugx(2, "%s: unable to get output.frequency.nominal", __func__); return -1; } ofn = strtod(outfreqnom, NULL); switch (val) { case 40: /* P01/P09 ofn==50.0 */ if (ofn != 50.0) return -1; if (protocol == 1 || protocol == 9) return 0; break; case 46: /* P02/P03/P10/P13/P14/P99 ofn==50.0 */ if (ofn != 50.0) return -1; if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99) return 0; break; case 49: /* P01/P09/P02/P03/P10/P13/P14/P99 ofn==50.0 */ if (ofn != 50.0) return -1; if (protocol == 1 || protocol == 2 || protocol == 3 || protocol == 9 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99) return 0; break; case 50: /* P01/P09 ofn==60.0 */ if (ofn != 60.0) return -1; if (protocol == 1 || protocol == 9) return 0; break; case 56: /* P02/P03/P10/P13/P14/P99 ofn==60.0 */ if (ofn != 60.0) return -1; if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99) return 0; break; case 59: /* P01/P09/P02/P03/P10/P13/P14/P99 ofn==60.0 */ if (ofn != 60.0) return -1; if (protocol == 1 || protocol == 2 || protocol == 3 || protocol == 9 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99) return 0; break; default: upsdebugx(2, "%s: unknown value (%s)", __func__, value); break; } return -1; } /* Range for Eco Mode maximum voltage: filled at runtime by voltronic_eco_volt */ static info_rw_t voltronic_r_eco_volt_max[] = { { "", 0 }, { "", 0 }, { "", 0 } }; /* Range for ECO Mode minimum voltage: filled at runtime by voltronic_eco_volt */ static info_rw_t voltronic_r_eco_volt_min[] = { { "", 0 }, { "", 0 }, { "", 0 } }; /* Range for ECO Mode minimum frequency */ static info_rw_t voltronic_r_eco_freq_min[] = { { "40.0", voltronic_eco_freq_min }, /* P01/P09 ofn==50.0 */ { "46.0", voltronic_eco_freq_min }, /* P02/P03/P10/P13/P14/P99 ofn==50.0 */ { "47.0", voltronic_eco_freq_min }, /* P01/P09 ofn==50.0 */ { "48.0", voltronic_eco_freq_min }, /* P02/P03/P10/P13/P14/P99 ofn==50.0 */ { "50.0", voltronic_eco_freq_min }, /* P01/P09 ofn==60.0 */ { "56.0", voltronic_eco_freq_min }, /* P02/P03/P10/P13/P14/P99 ofn==60.0 */ { "57.0", voltronic_eco_freq_min }, /* P01/P09 ofn==60.0 */ { "58.0", voltronic_eco_freq_min }, /* P02/P03/P10/P13/P14/P99 ofn==60.0 */ { "", 0 } }; /* Preprocess range value for ECO Mode minimum frequency */ static int voltronic_eco_freq_min(char *value, size_t len) { int protocol = strtol(dstate_getinfo("ups.firmware.aux")+1, NULL, 10), val = strtol(value, NULL, 10); double ofn; const char *outfreqnom = dstate_getinfo("output.frequency.nominal"); if (!outfreqnom) { upsdebugx(2, "%s: unable to get output.frequency.nominal", __func__); return -1; } ofn = strtod(outfreqnom, NULL); switch (val) { case 40: /* P01/P09 ofn==50.0 */ if (ofn != 50.0) return -1; if (protocol == 1 || protocol == 9) return 0; break; case 46: /* P02/P03/P10/P13/P14/P99 ofn==50.0 */ if (ofn != 50.0) return -1; if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99) return 0; break; case 47: /* P01/P09 ofn==50.0 */ if (ofn != 50.0) return -1; if (protocol == 1 || protocol == 9) return 0; break; case 48: /* P02/P03/P10/P13/P14/P99 ofn==50.0 */ if (ofn != 50.0) return -1; if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99) return 0; break; case 50: /* P01/P09 ofn==60.0 */ if (ofn != 60.0) return -1; if (protocol == 1 || protocol == 9) return 0; break; case 56: /* P02/P03/P10/P13/P14/P99 ofn==60.0 */ if (ofn != 60.0) return -1; if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99) return 0; break; case 57: /* P01/P09 ofn==60.0 */ if (ofn != 60.0) return -1; if (protocol == 1 || protocol == 9) return 0; break; case 58: /* P02/P03/P10/P13/P14/P99 ofn==60.0 */ if (ofn != 60.0) return -1; if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99) return 0; break; default: upsdebugx(2, "%s: unknown value (%s)", __func__, value); break; } return -1; } /* Range for ECO Mode maximum frequency */ static info_rw_t voltronic_r_eco_freq_max[] = { { "52.0", voltronic_eco_freq_max }, /* P02/P03/P10/P13/P14/P99 ofn==50.0 */ { "53.0", voltronic_eco_freq_max }, /* P01/P09 ofn==50.0 */ { "54.0", voltronic_eco_freq_max }, /* P02/P03/P10/P13/P14/P99 ofn==50.0 */ { "60.0", voltronic_eco_freq_max }, /* P01/P09 ofn==50.0 */ { "62.0", voltronic_eco_freq_max }, /* P02/P03/P10/P13/P14/P99 ofn==60.0 */ { "63.0", voltronic_eco_freq_max }, /* P01/P09 ofn==60.0 */ { "64.0", voltronic_eco_freq_max }, /* P02/P03/P10/P13/P14/P99 ofn==60.0 */ { "70.0", voltronic_eco_freq_max }, /* P01/P09 ofn==60.0 */ { "", 0 } }; /* Preprocess range value for ECO Mode maximum frequency */ static int voltronic_eco_freq_max(char *value, size_t len) { int protocol = strtol(dstate_getinfo("ups.firmware.aux")+1, NULL, 10), val = strtol(value, NULL, 10); double ofn; const char *outfreqnom = dstate_getinfo("output.frequency.nominal"); if (!outfreqnom) { upsdebugx(2, "%s: unable to get output.frequency.nominal", __func__); return -1; } ofn = strtod(outfreqnom, NULL); switch (val) { case 52: /* P02/P03/P10/P13/P14/P99 ofn==50.0 */ if (ofn != 50.0) return -1; if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99) return 0; break; case 53: /* P01/P09 ofn==50.0 */ if (ofn != 50.0) return -1; if (protocol == 1 || protocol == 9) return 0; break; case 54: /* P02/P03/P10/P13/P14/P99 ofn==50.0 */ if (ofn != 50.0) return -1; if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99) return 0; break; case 60: /* P01/P09 ofn==50.0 */ if (ofn != 50.0) return -1; if (protocol == 1 || protocol == 9) return 0; break; case 62: /* P02/P03/P10/P13/P14/P99 ofn==60.0 */ if (ofn != 60.0) return -1; if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99) return 0; break; case 63: /* P01/P09 ofn==60.0 */ if (ofn != 60.0) return -1; if (protocol == 1 || protocol == 9) return 0; break; case 64: /* P02/P03/P10/P13/P14/P99 ofn==60.0 */ if (ofn != 60.0) return -1; if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99) return 0; break; case 70: /* P01/P09 ofn==60.0 */ if (ofn != 60.0) return -1; if (protocol == 1 || protocol == 9) return 0; break; default: upsdebugx(2, "%s: unknown value (%s)", __func__, value); break; } return -1; } /* Enumlist for UPS capabilities that have a NUT var */ static info_rw_t voltronic_e_cap[] = { { "no", 0 }, { "yes", 0 }, { "", 0 } }; /* Enumlist for NONUT capabilities */ static info_rw_t voltronic_e_cap_nonut[] = { { "enabled", 0 }, { "disabled", 0 }, { "", 0 } }; /* == qx2nut lookup table == */ static item_t voltronic_qx2nut[] = { /* Query UPS for protocol * > [QPI\r] * < [(PI00\r] * 012345 * 0 */ { "ups.firmware.aux", 0, NULL, "QPI\r", "", 6, '(', "", 1, 4, "%s", QX_FLAG_STATIC, NULL, voltronic_protocol }, /* Query UPS for ratings * > [QRI\r] * < [(230.0 004 024.0 50.0\r] * 0123456789012345678901 * 0 1 2 */ { "output.voltage.nominal", 0, NULL, "QRI\r", "", 22, '(', "", 1, 5, "%.1f", QX_FLAG_STATIC, NULL, NULL }, { "output.current.nominal", 0, NULL, "QRI\r", "", 22, '(', "", 7, 9, "%.0f", QX_FLAG_STATIC, NULL, NULL }, { "battery.voltage.nominal", 0, NULL, "QRI\r", "", 22, '(', "", 11, 15, "%.1f", QX_FLAG_SEMI_STATIC, NULL, NULL }, /* as *per battery pack*: the value will change when the number of batteries is changed (battery_number through BATNn) */ { "output.frequency.nominal", 0, NULL, "QRI\r", "", 22, '(', "", 17, 20, "%.1f", QX_FLAG_STATIC, NULL, NULL }, /* Query UPS for ratings * > [QMD\r] * < [(#######OLHVT1K0 ###1000 80 1/1 230 230 02 12.0\r] <- Some UPS may reply with spaces instead of hashes * 012345678901234567890123456789012345678901234567 * 0 1 2 3 4 */ { "device.model", 0, NULL, "QMD\r", "", 48, '(', "", 1, 15, "%s", QX_FLAG_STATIC | QX_FLAG_TRIM, NULL, NULL }, { "ups.power.nominal", 0, NULL, "QMD\r", "", 48, '(', "", 17, 23, "%s", QX_FLAG_STATIC | QX_FLAG_TRIM, NULL, NULL }, { "output.powerfactor", 0, NULL, "QMD\r", "", 48, '(', "", 25, 26, "%.1f", QX_FLAG_STATIC, NULL, voltronic_output_powerfactor }, { "input.phases", 0, NULL, "QMD\r", "", 48, '(', "", 28, 28, "%.0f", QX_FLAG_STATIC, NULL, NULL }, { "output.phases", 0, NULL, "QMD\r", "", 48, '(', "", 30, 30, "%.0f", QX_FLAG_STATIC, NULL, NULL }, { "input.voltage.nominal", 0, NULL, "QMD\r", "", 48, '(', "", 32, 34, "%.1f", QX_FLAG_STATIC, NULL, NULL }, { "output.voltage.nominal", 0, NULL, "QMD\r", "", 48, '(', "", 36, 38, "%.1f", QX_FLAG_STATIC, NULL, NULL }, /* redundant with value from QRI */ /* { "battery_number", ST_FLAG_RW, voltronic_r_batt_numb, "QMD\r", "", 48, '(', "", 40, 41, "%d", QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_NONUT, NULL, voltronic_batt_numb }, *//* redundant with value from QBV */ /* { "battery.voltage.nominal", 0, NULL, "QMD\r", "", 48, '(', "", 43, 46, "%.1f", QX_FLAG_STATIC, NULL, NULL }, *//* as *per battery* vs *per pack* reported by QRI */ /* Query UPS for ratings * > [F\r] * < [#220.0 000 024.0 50.0\r] * 0123456789012345678901 * 0 1 2 */ { "input.voltage.nominal", 0, NULL, "F\r", "", 22, '#', "", 1, 5, "%.1f", QX_FLAG_STATIC, NULL, NULL }, { "input.current.nominal", 0, NULL, "F\r", "", 22, '#', "", 7, 9, "%.1f", QX_FLAG_STATIC, NULL, NULL }, { "battery.voltage.nominal", 0, NULL, "F\r", "", 22, '#', "", 11, 15, "%.1f", QX_FLAG_STATIC, NULL, NULL }, { "input.frequency.nominal", 0, NULL, "F\r", "", 22, '#', "", 17, 20, "%.1f", QX_FLAG_STATIC, NULL, NULL }, /* Query UPS for manufacturer * > [QMF\r] * < [(#######BOH\r] <- I don't know if it has a fixed length (-> so min length = ( + \r = 2). Hashes may be replaced by spaces * 012345678901 * 0 1 */ { "device.mfr", 0, NULL, "QMF\r", "", 2, '(', "", 1, 0, "%s", QX_FLAG_STATIC | QX_FLAG_TRIM, NULL, NULL }, /* Query UPS for firmware version * > [QVFW\r] * < [(VERFW:00322.02\r] * 0123456789012345 * 0 1 */ { "ups.firmware", 0, NULL, "QVFW\r", "", 16, '(', "", 7, 14, "%s", QX_FLAG_STATIC, NULL, NULL }, /* Query UPS for serial number * > [QID\r] * < [(12345679012345\r] <- As far as I know it hasn't a fixed length -> min length = ( + \r = 2 * 0123456789012345 * 0 1 */ { "device.serial", 0, NULL, "QID\r", "", 2, '(', "", 1, 0, "%s", QX_FLAG_STATIC, NULL, voltronic_serial_numb }, /* Query UPS for vendor infos * > [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 }, { "device.model", 0, NULL, "I\r", "", 39, '#', "", 17, 26, "%s", QX_FLAG_STATIC | QX_FLAG_TRIM, NULL, NULL }, { "ups.firmware", 0, NULL, "I\r", "", 39, '#', "", 28, 37, "%s", QX_FLAG_STATIC | QX_FLAG_TRIM, NULL, NULL }, /* Query UPS for status * > [QGS\r] * < [(234.9 50.0 229.8 50.0 000.0 000 369.1 ---.- 026.5 ---.- 018.8 100000000001\r] * 0123456789012345678901234567890123456789012345678901234567890123456789012345 * 0 1 2 3 4 5 6 7 */ { "input.voltage", 0, NULL, "QGS\r", "", 76, '(', "", 1, 5, "%.1f", 0, NULL, NULL }, { "input.frequency", 0, NULL, "QGS\r", "", 76, '(', "", 7, 10, "%.1f", 0, NULL, NULL }, { "output.voltage", 0, NULL, "QGS\r", "", 76, '(', "", 12, 16, "%.1f", 0, NULL, NULL }, { "output.frequency", 0, NULL, "QGS\r", "", 76, '(', "", 18, 21, "%.1f", 0, NULL, NULL }, { "output.current", 0, NULL, "QGS\r", "", 76, '(', "", 23, 27, "%.1f", 0, NULL, NULL }, { "ups.load", 0, NULL, "QGS\r", "", 76, '(', "", 29, 31, "%.0f", 0, NULL, NULL }, /* { "unknown.1", 0, NULL, "QGS\r", "", 76, '(', "", 33, 37, "%.1f", 0, NULL, NULL }, *//* Unknown */ /* { "unknown.2", 0, NULL, "QGS\r", "", 76, '(', "", 39, 43, "%.1f", 0, NULL, NULL }, *//* Unknown */ { "battery.voltage", 0, NULL, "QGS\r", "", 76, '(', "", 45, 49, "%.2f", 0, NULL, NULL }, /* { "unknown.3", 0, NULL, "QGS\r", "", 76, '(', "", 51, 55, "%.1f", 0, NULL, NULL }, *//* Unknown */ { "ups.temperature", 0, NULL, "QGS\r", "", 76, '(', "", 57, 61, "%.1f", 0, NULL, NULL }, { "ups.type", 0, NULL, "QGS\r", "", 76, '(', "", 63, 64, "%s", QX_FLAG_SEMI_STATIC, NULL, voltronic_status }, { "ups.status", 0, NULL, "QGS\r", "", 76, '(', "", 65, 65, "%s", QX_FLAG_QUICK_POLL, NULL, voltronic_status }, /* Utility Fail (Immediate) */ { "ups.status", 0, NULL, "QGS\r", "", 76, '(', "", 66, 66, "%s", QX_FLAG_QUICK_POLL, NULL, voltronic_status }, /* Battery Low */ { "ups.status", 0, NULL, "QGS\r", "", 76, '(', "", 67, 67, "%s", QX_FLAG_QUICK_POLL, NULL, voltronic_status }, /* Bypass/Boost or Buck Active */ { "ups.alarm", 0, NULL, "QGS\r", "", 76, '(', "", 67, 67, "%s", 0, NULL, voltronic_status }, /* Bypass/Boost or Buck Active */ { "ups.alarm", 0, NULL, "QGS\r", "", 76, '(', "", 68, 68, "%s", 0, NULL, voltronic_status }, /* UPS Fault */ /* { "unknown.4", 0, NULL, "QGS\r", "", 76, '(', "", 69, 69, "%s", 0, NULL, voltronic_status }, *//* Unknown */ { "ups.status", 0, NULL, "QGS\r", "", 76, '(', "", 70, 70, "%s", QX_FLAG_QUICK_POLL, NULL, voltronic_status }, /* Test in Progress */ { "ups.status", 0, NULL, "QGS\r", "", 76, '(', "", 71, 71, "%s", QX_FLAG_QUICK_POLL, NULL, voltronic_status }, /* Shutdown Active */ { "ups.beeper.status", 0, NULL, "QGS\r", "", 76, '(', "", 72, 72, "%s", 0, NULL, voltronic_status }, /* Beeper status - ups.beeper.status */ /* { "unknown.5", 0, NULL, "QGS\r", "", 76, '(', "", 73, 73, "%s", 0, NULL, voltronic_status }, *//* Unknown */ /* { "unknown.6", 0, NULL, "QGS\r", "", 76, '(', "", 74, 74, "%s", 0, NULL, voltronic_status }, *//* Unknown */ /* Query UPS for actual working mode * > [QMOD\r] * < [(S\r] * 012 * 0 */ { "ups.alarm", 0, NULL, "QMOD\r", "", 3, '(', "", 1, 1, "%s", 0, NULL, voltronic_mode }, { "ups.status", 0, NULL, "QMOD\r", "", 3, '(', "", 1, 1, "%s", 0, NULL, voltronic_mode }, /* Query UPS for faults and their type. Unskipped when a fault is found in 12bit flag of QGS, otherwise you'll get a fake reply. * > [QFS\r] * < [(OK\r] <- No fault * 0123 * 0 * < [(14 212.1 50.0 005.6 49.9 006 010.6 343.8 ---.- 026.2 021.8 01101100\r] <- Fault type + Short status * 012345678901234567890123456789012345678901234567890123456789012345678 * 0 1 2 3 4 5 6 */ { "ups.alarm", 0, NULL, "QFS\r", "", 4, '(', "", 1, 2, "%s", QX_FLAG_SKIP, NULL, voltronic_fault }, /* Query UPS for warnings and their type * > [QWS\r] * < [(0000000100000000000000000000000000000000000000000000000000000000\r] * 012345678901234567890123456789012345678901234567890123456789012345 * 0 1 2 3 4 5 6 */ { "ups.alarm", 0, NULL, "QWS\r", "", 66, '(', "", 1, 64, "%s", 0, NULL, voltronic_warning }, /* Query UPS for actual infos about battery * > [QBV\r] * < [(026.5 02 01 068 255\r] * 012345678901234567890 * 0 1 2 */ { "battery.voltage", 0, NULL, "QBV\r", "", 21, '(', "", 1, 5, "%.2f", 0, NULL, NULL }, { "battery_number", ST_FLAG_RW, voltronic_r_batt_numb, "QBV\r", "", 21, '(', "", 7, 9, "%d", QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_NONUT, NULL, voltronic_batt_numb }, /* Number of batteries that make a pack */ { "battery.packs", ST_FLAG_RW, voltronic_r_batt_packs, "QBV\r", "", 21, '(', "", 10, 11, "%.0f", QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE, NULL, NULL }, /* Number of battery packs in parallel */ { "battery.charge", 0, NULL, "QBV\r", "", 21, '(', "", 13, 15, "%.0f", 0, NULL, NULL }, { "battery.runtime", 0, NULL, "QBV\r", "", 21, '(', "", 17, 19, "%.0f", 0, NULL, voltronic_batt_runtime }, /* Query UPS for last seen min/max load level * > [QLDL\r] * < [(021 023\r] <- minimum load level - maximum load level * 012345678 * 0 */ { "output.power.minimum.percent", 0, NULL, "QLDL\r", "", 9, '(', "", 1, 3, "%.0f", 0, NULL, NULL }, { "output.power.maximum.percent", 0, NULL, "QLDL\r", "", 9, '(', "", 5, 7, "%.0f", 0, NULL, NULL }, /* Query UPS for multi-phase voltages/frequencies * > [Q3**\r] * < [(123.4 123.4 123.4 123.4 123.4 123.4\r] <- Q3PV * < [(123.4 123.4 123.4 123.4 123.4 123.4\r] <- Q3OV * < [(123 123 123\r] <- Q3PC * < [(123 123 123\r] <- Q3OC * < [(123 123 123\r] <- Q3LD * < [(123.4 123.4 123.4\r] <- Q3YV - P09 protocol * < [(123.4 123.4 123.4 123.4 123.4 123.4\r] <- Q3YV - P10/P03 protocols * 0123456789012345678901234567890123456 * 0 1 2 3 * * P09 = 2-phase input/2-phase output * Q3PV (Input Voltage L1 | Input Voltage L2 | Input Voltage L3 | Input Voltage L1-L2 | Input Voltage L1-L3 | Input Voltage L2-L3 * Q3OV (Output Voltage L1 | Output Voltage L2 | Output Voltage L3 | Output Voltage L1-L2 | Output Voltage L1-L3 | Output Voltage L2-L3 * Q3PC (Input Current L1 | Input Current L2 | Input Current L3 * Q3OC (Output Current L1 | Output Current L2 | Output Current L3 * Q3LD (Output Load Level L1 | Output Load Level L2 | Output Load Level L3 * Q3YV (Output Bypass Voltage L1 | Output Bypass Voltage L2 | Output Bypass Voltage L3 * * P10 = 3-phase input/3-phase output / P03 = 3-phase input/ 1-phase output * Q3PV (Input Voltage L1 | Input Voltage L2 | Input Voltage L3 | Input Voltage L1-L2 | Input Voltage L2-L3 | Input Voltage L1-L3 * Q3OV (Output Voltage L1 | Output Voltage L2 | Output Voltage L3 | Output Voltage L1-L2 | Output Voltage L2-L3 | Output Voltage L1-L3 * Q3PC (Input Current L1 | Input Current L2 | Input Current L3 * Q3OC (Output Current L1 | Output Current L2 | Output Current L3 * Q3LD (Output Load Level L1 | Output Load Level L2 | Output Load Level L3 * Q3YV (Output Bypass Voltage L1 | Output Bypass Voltage L2 | Output Bypass Voltage L3 | Output Bypass Voltage L1-L2 | Output Bypass Voltage L2-L3 | Output Bypass Voltage L1-L3 * */ /* From Q3PV */ { "input.L1-N.voltage", 0, NULL, "Q3PV\r", "", 37, '(', "", 1, 5, "%.1f", QX_FLAG_SKIP, NULL, NULL }, { "input.L2-N.voltage", 0, NULL, "Q3PV\r", "", 37, '(', "", 7, 11, "%.1f", QX_FLAG_SKIP, NULL, NULL }, { "input.L3-N.voltage", 0, NULL, "Q3PV\r", "", 37, '(', "", 13, 17, "%.1f", QX_FLAG_SKIP, NULL, NULL }, { "input.L1-L2.voltage", 0, NULL, "Q3PV\r", "", 37, '(', "", 19, 23, "%.1f", QX_FLAG_SKIP, NULL, NULL }, { "input.L2-L3.voltage", 0, NULL, "Q3PV\r", "", 37, '(', "", 25, 29, "%.1f", QX_FLAG_SKIP, NULL, NULL }, { "input.L1-L3.voltage", 0, NULL, "Q3PV\r", "", 37, '(', "", 31, 35, "%.1f", QX_FLAG_SKIP, NULL, NULL }, /* { "input.L1-L3.voltage", 0, NULL, "Q3PV\r", "", 37, '(', "", 25, 29, "%.1f", QX_FLAG_SKIP, NULL, NULL }, *//* P09 *//* Commented out because P09 should be two-phase input/output UPSes */ /* { "input.L2-L3.voltage", 0, NULL, "Q3PV\r", "", 37, '(', "", 31, 35, "%.1f", QX_FLAG_SKIP, NULL, NULL }, *//* P09 *//* Commented out because P09 should be two-phase input/output UPSes */ /* From Q3PC */ { "input.L1.current", 0, NULL, "Q3PC\r", "", 13, '(', "", 1, 3, "%.0f", QX_FLAG_SKIP, NULL, NULL }, { "input.L2.current", 0, NULL, "Q3PC\r", "", 13, '(', "", 5, 7, "%.0f", QX_FLAG_SKIP, NULL, NULL }, { "input.L3.current", 0, NULL, "Q3PC\r", "", 13, '(', "", 9, 11, "%.0f", QX_FLAG_SKIP, NULL, NULL }, /* From Q3OV */ { "output.L1-N.voltage", 0, NULL, "Q3OV\r", "", 37, '(', "", 1, 5, "%.1f", QX_FLAG_SKIP, NULL, NULL }, { "output.L2-N.voltage", 0, NULL, "Q3OV\r", "", 37, '(', "", 7, 11, "%.1f", QX_FLAG_SKIP, NULL, NULL }, { "output.L3-N.voltage", 0, NULL, "Q3OV\r", "", 37, '(', "", 13, 17, "%.1f", QX_FLAG_SKIP, NULL, NULL }, { "output.L1-L2.voltage", 0, NULL, "Q3OV\r", "", 37, '(', "", 19, 23, "%.1f", QX_FLAG_SKIP, NULL, NULL }, { "output.L2-L3.voltage", 0, NULL, "Q3OV\r", "", 37, '(', "", 25, 29, "%.1f", QX_FLAG_SKIP, NULL, NULL }, { "output.L1-L3.voltage", 0, NULL, "Q3OV\r", "", 37, '(', "", 31, 35, "%.1f", QX_FLAG_SKIP, NULL, NULL }, /* { "output.L1-L3.voltage", 0, NULL, "Q3OV\r", "", 37, '(', "", 25, 29, "%.1f", QX_FLAG_SKIP, NULL, NULL }, *//* P09 *//* Commented out because P09 should be two-phase input/output UPSes */ /* { "output.L2-L3.voltage", 0, NULL, "Q3OV\r", "", 37, '(', "", 31, 35, "%.1f", QX_FLAG_SKIP, NULL, NULL }, *//* P09 *//* Commented out because P09 should be two-phase input/output UPSes */ /* From Q3OC */ { "output.L1.current", 0, NULL, "Q3OC\r", "", 13, '(', "", 1, 3, "%.0f", QX_FLAG_SKIP, NULL, NULL }, { "output.L2.current", 0, NULL, "Q3OC\r", "", 13, '(', "", 5, 7, "%.0f", QX_FLAG_SKIP, NULL, NULL }, { "output.L3.current", 0, NULL, "Q3OC\r", "", 13, '(', "", 9, 11, "%.0f", QX_FLAG_SKIP, NULL, NULL }, /* From Q3LD */ { "output.L1.power.percent", 0, NULL, "Q3LD\r", "", 13, '(', "", 1, 3, "%.0f", QX_FLAG_SKIP, NULL, NULL }, { "output.L2.power.percent", 0, NULL, "Q3LD\r", "", 13, '(', "", 5, 7, "%.0f", QX_FLAG_SKIP, NULL, NULL }, { "output.L3.power.percent", 0, NULL, "Q3LD\r", "", 13, '(', "", 9, 11, "%.0f", QX_FLAG_SKIP, NULL, NULL }, /* From Q3YV */ { "output.bypass.L1-N.voltage", 0, NULL, "Q3YV\r", "", 37, '(', "", 1, 5, "%.1f", QX_FLAG_SKIP, NULL, NULL }, { "output.bypass.L2-N.voltage", 0, NULL, "Q3YV\r", "", 37, '(', "", 7, 11, "%.1f", QX_FLAG_SKIP, NULL, NULL }, { "output.bypass.L3-N.voltage", 0, NULL, "Q3YV\r", "", 37, '(', "", 13, 17, "%.1f", QX_FLAG_SKIP, NULL, NULL }, { "output.bypass.L1-N.voltage", 0, NULL, "Q3YV\r", "", 19, '(', "", 1, 5, "%.1f", QX_FLAG_SKIP, NULL, NULL }, /* P09 */ { "output.bypass.L2-N.voltage", 0, NULL, "Q3YV\r", "", 19, '(', "", 7, 11, "%.1f", QX_FLAG_SKIP, NULL, NULL }, /* P09 */ /* { "output.bypass.L3-N.voltage", 0, NULL, "Q3YV\r", "", 19, '(', "", 13, 17, "%.1f", QX_FLAG_SKIP, NULL, NULL }, *//* P09 *//* Commented out because P09 should be two-phase input/output UPSes */ { "output.bypass.L1-L2.voltage", 0, NULL, "Q3YV\r", "", 37, '(', "", 19, 23, "%.1f", QX_FLAG_SKIP, NULL, NULL }, { "output.bypass.L2-L3.voltage", 0, NULL, "Q3YV\r", "", 37, '(', "", 25, 29, "%.1f", QX_FLAG_SKIP, NULL, NULL }, { "output.bypass.L1-L3.voltage", 0, NULL, "Q3YV\r", "", 37, '(', "", 31, 35, "%.1f", QX_FLAG_SKIP, NULL, NULL }, /* Query UPS for capability - total options available: 23; only those whom the UPS is capable of are reported as Enabled or Disabled * > [QFLAG\r] * < [(EpashcDbroegfl\r] * 0123456789012345 * 0 1 * min length = ( + E + D + \r = 4 */ { "ups.start.auto", ST_FLAG_RW, voltronic_e_cap, "QFLAG\r", "", 4, '(', "", 1, 0, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_ENUM, NULL, voltronic_capability }, { "battery.protection", ST_FLAG_RW, voltronic_e_cap, "QFLAG\r", "", 4, '(', "", 1, 0, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_ENUM, NULL, voltronic_capability }, { "battery.energysave", ST_FLAG_RW, voltronic_e_cap, "QFLAG\r", "", 4, '(', "", 1, 0, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_ENUM, NULL, voltronic_capability }, { "ups.start.battery", ST_FLAG_RW, voltronic_e_cap, "QFLAG\r", "", 4, '(', "", 1, 0, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_ENUM, NULL, voltronic_capability }, { "outlet.0.switchable", ST_FLAG_RW, voltronic_e_cap, "QFLAG\r", "", 4, '(', "", 1, 0, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_ENUM, NULL, voltronic_capability }, /* Not available in NUT */ { "bypass_alarm", 0, NULL, "QFLAG\r", "", 4, '(', "", 1, 0, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT, NULL, voltronic_capability }, { "battery_alarm", 0, NULL, "QFLAG\r", "", 4, '(', "", 1, 0, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT, NULL, voltronic_capability }, { "bypass_when_off", 0, NULL, "QFLAG\r", "", 4, '(', "", 1, 0, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT, NULL, voltronic_capability }, { "alarm_control", 0, NULL, "QFLAG\r", "", 4, '(', "", 1, 0, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT, NULL, voltronic_capability }, { "converter_mode", 0, NULL, "QFLAG\r", "", 4, '(', "", 1, 0, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT, NULL, voltronic_capability }, { "eco_mode", 0, NULL, "QFLAG\r", "", 4, '(', "", 1, 0, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT, NULL, voltronic_capability }, { "battery_open_status_check", 0, NULL, "QFLAG\r", "", 4, '(', "", 1, 0, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT, NULL, voltronic_capability }, { "bypass_forbidding", 0, NULL, "QFLAG\r", "", 4, '(', "", 1, 0, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT, NULL, voltronic_capability }, { "site_fault_detection", 0, NULL, "QFLAG\r", "", 4, '(', "", 1, 0, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT, NULL, voltronic_capability }, { "advanced_eco_mode", 0, NULL, "QFLAG\r", "", 4, '(', "", 1, 0, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT, NULL, voltronic_capability }, { "constant_phase_angle", 0, NULL, "QFLAG\r", "", 4, '(', "", 1, 0, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT, NULL, voltronic_capability }, { "limited_runtime_on_battery", 0, NULL, "QFLAG\r", "", 4, '(', "", 1, 0, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT, NULL, voltronic_capability }, /* Enable or Disable or Reset to safe default values capability options * > [PEX\r] > [PDX\r] > [PF\r] * < [(ACK\r] < [(ACK\r] < [(ACK\r] * 01234 01234 01234 * 0 0 0 */ { "ups.start.auto", 0, voltronic_e_cap, "P%sR\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_SKIP, NULL, voltronic_capability_set }, { "battery.protection", 0, voltronic_e_cap, "P%sS\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_SKIP, NULL, voltronic_capability_set }, { "battery.energysave", 0, voltronic_e_cap, "P%sG\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_SKIP, NULL, voltronic_capability_set }, { "ups.start.battery", 0, voltronic_e_cap, "P%sC\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_SKIP, NULL, voltronic_capability_set }, { "outlet.0.switchable", 0, voltronic_e_cap, "P%sJ\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_SKIP, NULL, voltronic_capability_set }, /* Not available in NUT */ { "reset_to_default", 0, NULL, "PF\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, voltronic_capability_reset }, { "bypass_alarm", 0, voltronic_e_cap_nonut, "P%sP\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, voltronic_capability_set_nonut }, { "battery_alarm", 0, voltronic_e_cap_nonut, "P%sB\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, voltronic_capability_set_nonut }, { "bypass_when_off", 0, voltronic_e_cap_nonut, "P%sO\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, voltronic_capability_set_nonut }, { "alarm_control", 0, voltronic_e_cap_nonut, "P%sA\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, voltronic_capability_set_nonut }, { "converter_mode", 0, voltronic_e_cap_nonut, "P%sV\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, voltronic_capability_set_nonut }, { "eco_mode", 0, voltronic_e_cap_nonut, "P%sE\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, voltronic_capability_set_nonut }, { "battery_open_status_check", 0, voltronic_e_cap_nonut, "P%sD\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, voltronic_capability_set_nonut }, { "bypass_forbidding", 0, voltronic_e_cap_nonut, "P%sF\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, voltronic_capability_set_nonut }, { "site_fault_detection", 0, voltronic_e_cap_nonut, "P%sL\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, voltronic_capability_set_nonut }, { "advanced_eco_mode", 0, voltronic_e_cap_nonut, "P%sN\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, voltronic_capability_set_nonut }, { "constant_phase_angle", 0, voltronic_e_cap_nonut, "P%sQ\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, voltronic_capability_set_nonut }, { "limited_runtime_on_battery", 0, voltronic_e_cap_nonut, "P%sW\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, voltronic_capability_set_nonut }, /* Query UPS for programmable outlet (1-4) status * > [QSK1\r] * < [(1\r] <- if outlet is on -> (1 , if off -> (0 ; (NAK -> outlet is not programmable * 012 * 0 */ { "outlet.1.switchable", 0, NULL, "QSK1\r", "", 3, '(', "", 1, 1, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_SKIP, NULL, voltronic_outlet }, { "outlet.1.status", 0, NULL, "QSK1\r", "", 3, '(', "", 1, 1, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_SKIP, NULL, voltronic_outlet }, { "outlet.2.switchable", 0, NULL, "QSK2\r", "", 3, '(', "", 1, 1, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_SKIP, NULL, voltronic_outlet }, { "outlet.2.status", 0, NULL, "QSK2\r", "", 3, '(', "", 1, 1, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_SKIP, NULL, voltronic_outlet }, { "outlet.3.switchable", 0, NULL, "QSK3\r", "", 3, '(', "", 1, 1, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_SKIP, NULL, voltronic_outlet }, { "outlet.3.status", 0, NULL, "QSK3\r", "", 3, '(', "", 1, 1, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_SKIP, NULL, voltronic_outlet }, { "outlet.4.switchable", 0, NULL, "QSK4\r", "", 3, '(', "", 1, 1, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_SKIP, NULL, voltronic_outlet }, { "outlet.4.status", 0, NULL, "QSK4\r", "", 3, '(', "", 1, 1, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_SKIP, NULL, voltronic_outlet }, /* Query UPS for programmable outlet n (1-4) delay time before it shuts down the load when on battery mode * > [QSKT1\r] * < [(008\r] <- if delay time is set (by PSK[1-4]n) it'll report n (minutes) otherwise it'll report (NAK (also if outlet is not programmable) * 01234 * 0 */ { "outlet.1.delay.shutdown", ST_FLAG_RW, voltronic_r_outlet_delay, "QSKT1\r", "", 5, '(', "", 1, 3, "%.0f", QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_SKIP, NULL, voltronic_outlet_delay }, { "outlet.2.delay.shutdown", ST_FLAG_RW, voltronic_r_outlet_delay, "QSKT2\r", "", 5, '(', "", 1, 3, "%.0f", QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_SKIP, NULL, voltronic_outlet_delay }, { "outlet.3.delay.shutdown", ST_FLAG_RW, voltronic_r_outlet_delay, "QSKT3\r", "", 5, '(', "", 1, 3, "%.0f", QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_SKIP, NULL, voltronic_outlet_delay }, { "outlet.4.delay.shutdown", ST_FLAG_RW, voltronic_r_outlet_delay, "QSKT4\r", "", 5, '(', "", 1, 3, "%.0f", QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_SKIP, NULL, voltronic_outlet_delay }, /* Set delay time for programmable outlets * > [PSK1nnn\r] n = 0..9 * < [(ACK\r] * 01234 * 0 */ { "outlet.1.delay.shutdown", 0, voltronic_r_outlet_delay, "PSK1%03d\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_SKIP, NULL, voltronic_outlet_delay_set }, { "outlet.2.delay.shutdown", 0, voltronic_r_outlet_delay, "PSK2%03d\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_SKIP, NULL, voltronic_outlet_delay_set }, { "outlet.3.delay.shutdown", 0, voltronic_r_outlet_delay, "PSK3%03d\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_SKIP, NULL, voltronic_outlet_delay_set }, { "outlet.4.delay.shutdown", 0, voltronic_r_outlet_delay, "PSK4%03d\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_SKIP, NULL, voltronic_outlet_delay_set }, /* Query UPS for ECO Mode voltage limits * > [QHE\r] * < [(242 218\r] * 012345678 * 0 */ { "input.transfer.high", ST_FLAG_RW, voltronic_r_eco_volt_max, "QHE\r", "", 9, '(', "", 1, 3, "%.0f", QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_SKIP, NULL, voltronic_eco_volt }, { "input.transfer.low", ST_FLAG_RW, voltronic_r_eco_volt_min, "QHE\r", "", 9, '(', "", 5, 7, "%.0f", QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_SKIP, NULL, voltronic_eco_volt }, { "input.transfer.low.min", 0, NULL, "QHE\r", "", 9, '(', "", 5, 7, "%.0f", QX_FLAG_SEMI_STATIC | QX_FLAG_SKIP, NULL, voltronic_eco_volt_range }, { "input.transfer.low.max", 0, NULL, "QHE\r", "", 9, '(', "", 5, 7, "%.0f", QX_FLAG_SEMI_STATIC | QX_FLAG_SKIP, NULL, voltronic_eco_volt_range }, { "input.transfer.high.min", 0, NULL, "QHE\r", "", 9, '(', "", 1, 3, "%.0f", QX_FLAG_SEMI_STATIC | QX_FLAG_SKIP, NULL, voltronic_eco_volt_range }, { "input.transfer.high.max", 0, NULL, "QHE\r", "", 9, '(', "", 1, 3, "%.0f", QX_FLAG_SEMI_STATIC | QX_FLAG_SKIP, NULL, voltronic_eco_volt_range }, /* Set ECO Mode voltage limits * > [HEHnnn\r] > [HELnnn\r] n = 0..9 * < [(ACK\r] < [(ACK\r] * 01234 01234 * 0 0 */ { "input.transfer.high", 0, voltronic_r_eco_volt_max, "HEH%03.0f\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_SKIP, NULL, voltronic_process_setvar }, { "input.transfer.low", 0, voltronic_r_eco_volt_min, "HEL%03.0f\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_SKIP, NULL, voltronic_process_setvar }, /* Query UPS for ECO Mode frequency limits * > [QFRE\r] * < [(53.0 47.0\r] * 01234567890 * 0 1 */ { "input.frequency.high", ST_FLAG_RW, voltronic_r_eco_freq_max, "QFRE\r", "", 11, '(', "", 1, 4, "%.1f", QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_SKIP, NULL, voltronic_eco_freq }, { "input.frequency.low", ST_FLAG_RW, voltronic_r_eco_freq_min, "QFRE\r", "", 11, '(', "", 6, 9, "%.1f", QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_SKIP, NULL, voltronic_eco_freq }, /* Set ECO Mode frequency limits * > [FREHnn.n\r] > [FRELnn.n\r] n = 0..9 * < [(ACK\r] < [(ACK\r] * 01234 01234 * 0 0 */ { "input.frequency.high", 0, voltronic_r_eco_freq_max, "FREH%04.1f\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_SKIP, NULL, voltronic_process_setvar }, { "input.frequency.low", 0, voltronic_r_eco_freq_min, "FREL%04.1f\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_SKIP, NULL, voltronic_process_setvar }, /* Query UPS for Bypass Mode voltage limits * > [QBYV\r] * < [(264 170\r] * 012345678 * 0 */ { "max_bypass_volt", ST_FLAG_RW, voltronic_r_bypass_volt_max, "QBYV\r", "", 9, '(', "", 1, 3, "%.0f", QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, voltronic_bypass }, { "min_bypass_volt", ST_FLAG_RW, voltronic_r_bypass_volt_min, "QBYV\r", "", 9, '(', "", 5, 7, "%.0f", QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, voltronic_bypass }, /* Set Bypass Mode voltage limits * > [PHVnnn\r] > [PLVnnn\r] n = 0..9 * < [(ACK\r] < [(ACK\r] * 01234 01234 * 0 0 */ { "max_bypass_volt", 0, voltronic_r_bypass_volt_max, "PHV%03.0f\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, voltronic_process_setvar }, { "min_bypass_volt", 0, voltronic_r_bypass_volt_min, "PLV%03.0f\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, voltronic_process_setvar }, /* Query UPS for Bypass Mode frequency limits * > [QBYF\r] * < [(53.0 47.0\r] * 01234567890 * 0 1 */ { "max_bypass_freq", ST_FLAG_RW, voltronic_r_bypass_freq_max, "QBYF\r", "", 11, '(', "", 1, 4, "%.1f", QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, voltronic_bypass }, { "min_bypass_freq", ST_FLAG_RW, voltronic_r_bypass_freq_min, "QBYF\r", "", 11, '(', "", 6, 9, "%.1f", QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, voltronic_bypass }, /* Set Bypass Mode frequency limits * > [PGFnn.n\r] > [PSFnn.n\r] n = 0..9 * < [(ACK\r] < [(ACK\r] * 01234 01234 * 0 0 */ { "max_bypass_freq", 0, voltronic_r_bypass_freq_max, "PGF%04.1f\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, voltronic_process_setvar }, { "min_bypass_freq", 0, voltronic_r_bypass_freq_min, "PSF%04.1f\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, voltronic_process_setvar }, /* Set number of batteries that make a pack to n (integer, 1-9). NOTE: changing the number of batteries will change the UPS's estimation on battery charge/runtime * > [BATNn\r] * < [(ACK\r] * 01234 * 0 */ { "battery_number", 0, voltronic_r_batt_numb, "BATN%1.0f\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_NONUT, NULL, voltronic_process_setvar }, /* Set number of battery packs in parallel to n (integer, 01-99). NOTE: changing the number of battery packs will change the UPS's estimation on battery charge/runtime * > [BATGNn\r] * < [(ACK\r] * 01234 * 0 */ { "battery.packs", 0, voltronic_r_batt_packs, "BATGN%02.0f\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_RANGE, NULL, voltronic_process_setvar }, /* Query UPS for battery type (Only P31) * > [QBT\r] * < [(01\r] <- 00="Li", 01="Flooded" or 02="AGM" * 0123 * 0 */ { "battery.type", ST_FLAG_RW, voltronic_e_batt_type, "QBT\r", "", 4, '(', "", 1, 2, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_ENUM | QX_FLAG_SKIP, NULL, voltronic_p31b }, /* Set battery type (Only P31) * > [PBTnn\r] nn = 00/01/02 * < [(ACK\r] * 01234 * 0 */ { "battery.type", 0, voltronic_e_batt_type, "PBT%02.0f\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_SKIP, NULL, voltronic_p31b_set }, /* Query UPS for device grid working range (Only P31) * > [QGR\r] * < [(01\r] <- 00=Appliance, 01=UPS * 0123 * 0 */ { "work_range_type", ST_FLAG_RW, voltronic_e_work_range, "QGR\r", "", 4, '(', "", 1, 2, "%s", QX_FLAG_SEMI_STATIC | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, voltronic_p31g }, /* Set device grid working range type (Only P31) * > [PBTnn\r] nn = 00/01 * < [(ACK\r] * 01234 * 0 */ { "work_range_type", 0, voltronic_e_work_range, "PGR%02.0f\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, voltronic_p31g_set }, /* Query UPS for battery low voltage * > [RE0\r] * < [#20\r] * 012 * 0 */ { "battery.voltage.low", ST_FLAG_RW, voltronic_r_batt_low, "RE0\r", "", 3, '#', "", 1, 2, "%.1f", QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE, NULL, NULL }, /* Set voltage for battery low to n (integer, 20..24/20..28). NOTE: changing the battery low voltage will change the UPS's estimation on battery charge/runtime * > [W0En\r] * < [(ACK\r] * 01234 * 0 */ { "battery.voltage.low", 0, voltronic_r_batt_low, "W0E%02.0f\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_RANGE, NULL, voltronic_process_setvar }, /* Query UPS for Phase Angle * > [QPD\r] * < [(000 120\r] <- Input Phase Angle - Output Phase Angle * 012345678 * 0 */ { "input_phase_angle", 0, NULL, "QPD\r", "", 9, '(', "", 1, 3, "%03d", QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT, NULL, voltronic_phase }, { "output_phase_angle", ST_FLAG_RW, voltronic_e_phase, "QPD\r", "", 9, '(', "", 5, 7, "%03d", QX_FLAG_SEMI_STATIC | QX_FLAG_ENUM | QX_FLAG_NONUT, NULL, voltronic_phase }, /* Set output phase angle * > [PPDn\r] n = (000, 120, 180 or 240) * < [(ACK\r] * 01234 * 0 */ { "output_phase_angle", 0, voltronic_e_phase, "PPD%03.0f\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, voltronic_phase_set }, /* Query UPS for master/slave for a system of UPSes in parallel * > [QPAR\r] * < [(001\r] <- 001 for master UPS, 002 and 003 for slave UPSes * 01234 * 0 */ { "voltronic_parallel", 0, NULL, "QPAR\r", "", 5, '(', "", 1, 3, "%s", QX_FLAG_STATIC | QX_FLAG_NONUT, NULL, voltronic_parallel }, /* Query UPS for ?? * > [QBDR\r] * < [(1234\r] <- unknown reply - My UPS (NAK at me * 012345 * 0 */ { "unknown.7", 0, NULL, "QBDR\r", "", 5, '(', "", 1, 0, "%s", QX_FLAG_STATIC | QX_FLAG_NONUT | QX_FLAG_SKIP, NULL, NULL }, /* Instant commands */ { "load.off", 0, NULL, "SOFF\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_CMD, NULL, NULL }, { "load.on", 0, NULL, "SON\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_CMD, NULL, NULL }, { "shutdown.return", 0, NULL, "S%s\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_CMD, NULL, voltronic_process_command }, { "shutdown.stayoff", 0, NULL, "S%sR0000\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_CMD, NULL, voltronic_process_command }, { "shutdown.stop", 0, NULL, "CS\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_CMD, NULL, NULL }, { "test.battery.start", 0, NULL, "T%s\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_CMD, NULL, voltronic_process_command }, { "test.battery.start.deep", 0, NULL, "TL\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_CMD, NULL, NULL }, { "test.battery.start.quick", 0, NULL, "T\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_CMD, NULL, NULL }, { "test.battery.stop", 0, NULL, "CT\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_CMD, NULL, NULL }, { "beeper.toggle", 0, NULL, "BZ%s\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_CMD, NULL, voltronic_process_command }, /* Enable/disable beeper: unskipped if the UPS can control alarm (capability) */ { "beeper.enable", 0, NULL, "PEA\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_CMD | QX_FLAG_SKIP, NULL, NULL }, { "beeper.disable", 0, NULL, "PDA\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_CMD | QX_FLAG_SKIP, NULL, NULL }, /* Outlet control: unskipped if the outlets are manageable */ { "outlet.1.load.off", 0, NULL, "SKOFF1\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_CMD | QX_FLAG_SKIP, NULL, NULL }, { "outlet.1.load.on", 0, NULL, "SKON1\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_CMD | QX_FLAG_SKIP, NULL, NULL }, { "outlet.2.load.off", 0, NULL, "SKOFF2\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_CMD | QX_FLAG_SKIP, NULL, NULL }, { "outlet.2.load.on", 0, NULL, "SKON2\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_CMD | QX_FLAG_SKIP, NULL, NULL }, { "outlet.3.load.off", 0, NULL, "SKOFF3\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_CMD | QX_FLAG_SKIP, NULL, NULL }, { "outlet.3.load.on", 0, NULL, "SKON3\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_CMD | QX_FLAG_SKIP, NULL, NULL }, { "outlet.4.load.off", 0, NULL, "SKOFF4\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_CMD | QX_FLAG_SKIP, NULL, NULL }, { "outlet.4.load.on", 0, NULL, "SKON4\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_CMD | QX_FLAG_SKIP, NULL, NULL }, /* Bypass: unskipped if the UPS is capable of ECO Mode */ { "bypass.start", 0, NULL, "PEE\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_CMD | QX_FLAG_SKIP, NULL, NULL }, { "bypass.stop", 0, NULL, "PDE\r", "", 5, '(', "", 1, 3, NULL, QX_FLAG_CMD | QX_FLAG_SKIP, NULL, NULL }, /* Server-side settable vars */ { "ups.delay.start", ST_FLAG_RW, voltronic_r_ondelay, NULL, "", 0, 0, "", 0, 0, DEFAULT_ONDELAY, QX_FLAG_ABSENT | QX_FLAG_SETVAR | QX_FLAG_RANGE, NULL, voltronic_process_setvar }, { "ups.delay.shutdown", ST_FLAG_RW, voltronic_r_offdelay, NULL, "", 0, 0, "", 0, 0, DEFAULT_OFFDELAY, QX_FLAG_ABSENT | QX_FLAG_SETVAR | QX_FLAG_RANGE, NULL, voltronic_process_setvar }, /* End of structure. */ { NULL, 0, NULL, NULL, "", 0, 0, "", 0, 0, NULL, 0, NULL, NULL } }; /* == Testing table == */ #ifdef TESTING static testing_t voltronic_testing[] = { { "QGS\r", "(234.9 50.0 229.8 50.0 000.0 00A 369.1 ---.- 026.5 ---.- 018.8 100000000001\r", -1 }, { "QPI\r", "(PI01\r", -1 }, { "QRI\r", "(230.0 004 024.0 50.0\r", -1 }, { "QMF\r", "(#####VOLTRONIC\r", -1 }, { "I\r", "#------------- ------ VT12046Q \r", -1 }, { "F\r", "#220.0 000 024.0 50.0\r", -1 }, { "QMD\r", "(#######OLHVT1K0 ###1000 80 2/2 230 230 02 12.0\r", -1 }, { "QFS\r", "(14 212.1 50.0 005.6 49.9 006 010.6 343.8 ---.- 026.2 021.8 01101100\r", -1 }, { "QMOD\r", "(S\r", -1 }, { "QVFW\r", "(VERFW:00322.02\r", -1 }, { "QID\r", "(685653211455\r", -1 }, { "QBV\r", "(026.5 02 01 068 255\r", -1 }, { "QFLAG\r", "(EpashcjDbroegfl\r", -1 }, { "QWS\r", "(0000000000000000000000000000000000000000000000000000000001000001\r", -1 }, { "QHE\r", "(242 218\r", -1 }, { "QBYV\r", "(264 170\r", -1 }, { "QBYF\r", "(53.0 47.0\r", -1 }, { "QSK1\r", "(1\r", -1 }, { "QSK2\r", "(0\r", -1 }, { "QSK3\r", "(1\r", -1 }, { "QSK4\r", "(NAK\r", -1 }, { "QSKT1\r", "(008\r", -1 }, { "QSKT2\r", "(012\r", -1 }, { "QSKT3\r", "(NAK\r", -1 }, { "QSKT4\r", "(007\r", -1 }, { "RE0\r", "#20\r", -1 }, { "W0E24\r", "(ACK\r", -1 }, { "PF\r", "(ACK\r", -1 }, { "PEA\r", "(ACK\r", -1 }, { "PDR\r", "(NAK\r", -1 }, { "HEH250\r", "(ACK\r", -1 }, { "HEL210\r", "(ACK\r", -1 }, { "PHV260\r", "(NAK\r", -1 }, { "PLV190\r", "(ACK\r", -1 }, { "PGF51.0\r", "(NAK\r", -1 }, { "PSF47.5\r", "(ACK\r", -1 }, { "BATN2\r", "(ACK\r", -1 }, { "BATGN04\r", "(ACK\r", -1 }, { "QBT\r", "(01\r", -1 }, { "PBT02\r", "(ACK\r", -1 }, { "QGR\r", "(00\r", -1 }, { "PGR01\r", "(ACK\r", -1 }, { "PSK1008\r", "(ACK\r", -1 }, { "PSK3987\r", "(ACK\r", -1 }, { "PSK2009\r", "(ACK\r", -1 }, { "PSK4012\r", "(ACK\r", -1 }, { "Q3PV\r", "(123.4 456.4 789.4 012.4 323.4 223.4\r", -1 }, { "Q3OV\r", "(253.4 163.4 023.4 143.4 103.4 523.4\r", -1 }, { "Q3OC\r", "(109 069 023\r", -1 }, { "Q3LD\r", "(005 033 089\r", -1 }, { "Q3YV\r", "(303.4 245.4 126.4 222.4 293.4 321.4\r", -1 }, { "Q3PC\r", "(002 023 051\r", -1 }, { "SOFF\r", "(NAK\r", -1 }, { "SON\r", "(ACK\r", -1 }, { "T\r", "(NAK\r", -1 }, { "TL\r", "(ACK\r", -1 }, { "CS\r", "(ACK\r", -1 }, { "CT\r", "(NAK\r", -1 }, { "BZOFF\r", "(ACK\r", -1 }, { "BZON\r", "(ACK\r", -1 }, { "S.3R0002\r", "(ACK\r", -1 }, { "S02R0024\r", "(NAK\r", -1 }, { "S.5\r", "(ACK\r", -1 }, { "T.3\r", "(ACK\r", -1 }, { "T02\r", "(NAK\r", -1 }, { "SKON1\r", "(ACK\r", -1 }, { "SKOFF1\r", "(NAK\r", -1 }, { "SKON2\r", "(ACK\r", -1 }, { "SKOFF2\r", "(ACK\r", -1 }, { "SKON3\r", "(NAK\r", -1 }, { "SKOFF3\r", "(ACK\r", -1 }, { "SKON4\r", "(NAK\r", -1 }, { "SKOFF4\r", "(NAK\r", -1 }, { "QPAR\r", "(003\r", -1 }, { "QPD\r", "(000 240\r", -1 }, { "PPD120\r", "(ACK\r", -1 }, { "QLDL\r", "(005 080\r", -1 }, { "QBDR\r", "(1234\r", -1 }, { "QFRE\r", "(50.0 00.0\r", -1 }, { "FREH54.0\r", "(ACK\r", -1 }, { "FREL47.0\r", "(ACK\r", -1 }, { "PEP\r", "(ACK\r", -1 }, { "PDP\r", "(ACK\r", -1 }, { "PEB\r", "(ACK\r", -1 }, { "PDB\r", "(ACK\r", -1 }, { "PER\r", "(NAK\r", -1 }, { "PDR\r", "(NAK\r", -1 }, { "PEO\r", "(ACK\r", -1 }, { "PDO\r", "(ACK\r", -1 }, { "PEA\r", "(ACK\r", -1 }, { "PDA\r", "(ACK\r", -1 }, { "PES\r", "(ACK\r", -1 }, { "PDS\r", "(ACK\r", -1 }, { "PEV\r", "(ACK\r", -1 }, { "PDV\r", "(ACK\r", -1 }, { "PEE\r", "(ACK\r", -1 }, { "PDE\r", "(ACK\r", -1 }, { "PEG\r", "(ACK\r", -1 }, { "PDG\r", "(NAK\r", -1 }, { "PED\r", "(ACK\r", -1 }, { "PDD\r", "(ACK\r", -1 }, { "PEC\r", "(ACK\r", -1 }, { "PDC\r", "(NAK\r", -1 }, { "PEF\r", "(NAK\r", -1 }, { "PDF\r", "(ACK\r", -1 }, { "PEJ\r", "(NAK\r", -1 }, { "PDJ\r", "(ACK\r", -1 }, { "PEL\r", "(ACK\r", -1 }, { "PDL\r", "(ACK\r", -1 }, { "PEN\r", "(ACK\r", -1 }, { "PDN\r", "(ACK\r", -1 }, { "PEQ\r", "(ACK\r", -1 }, { "PDQ\r", "(ACK\r", -1 }, { "PEW\r", "(NAK\r", -1 }, { "PDW\r", "(ACK\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 voltronic_claim(void) { /* We need at least QGS and QPI 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 Protocol */ item = find_nut_info("ups.firmware.aux", 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/Protocol out of range */ if (ups_infoval_set(item) != 1) { dstate_delinfo("input.voltage"); return 0; } return 1; } /* Subdriver-specific flags/vars */ static void voltronic_makevartable(void) { /* Capability vars */ addvar(VAR_FLAG, "reset_to_default", "Reset capability options and their limits to safe default values"); addvar(VAR_VALUE, "bypass_alarm", "Alarm (BEEP!) at Bypass Mode [enabled/disabled]"); addvar(VAR_VALUE, "battery_alarm", "Alarm (BEEP!) at Battery Mode [enabled/disabled]"); addvar(VAR_VALUE, "bypass_when_off", "Bypass when the UPS is Off [enabled/disabled]"); addvar(VAR_VALUE, "alarm_control", "Alarm (BEEP!) Control [enabled/disabled]"); addvar(VAR_VALUE, "converter_mode", "Converter Mode [enabled/disabled]"); addvar(VAR_VALUE, "eco_mode", "ECO Mode [enabled/disabled]"); addvar(VAR_VALUE, "battery_open_status_check", "Battery Open Status Check [enabled/disabled]"); addvar(VAR_VALUE, "bypass_forbidding", "Bypass not allowed (Bypass Forbidding) [enabled/disabled]"); addvar(VAR_VALUE, "site_fault_detection", "Site fault detection [enabled/disabled]"); addvar(VAR_VALUE, "advanced_eco_mode", "Advanced ECO Mode [enabled/disabled]"); addvar(VAR_VALUE, "constant_phase_angle", "Constant Phase Angle Function (Output and input phase angles are not equal) [enabled/disabled]"); addvar(VAR_VALUE, "limited_runtime_on_battery", "Limited runtime on battery mode [enabled/disabled]"); /* Bypass Mode frequency/voltage limits */ addvar(VAR_VALUE, "max_bypass_volt", "Maximum voltage for Bypass Mode"); addvar(VAR_VALUE, "min_bypass_volt", "Minimum voltage for Bypass Mode"); addvar(VAR_VALUE, "max_bypass_freq", "Maximum frequency for Bypass Mode"); addvar(VAR_VALUE, "min_bypass_freq", "Minimum frequency for Bypass Mode"); /* Device grid working range type for P31 UPSes */ addvar(VAR_VALUE, "work_range_type", "Device grid working range for P31 UPSes [Appliance/UPS]"); /* Output phase angle */ addvar(VAR_VALUE, "output_phase_angle", "Change output phase angle to the provided value [000, 120, 180, 240]°"); /* Number of batteries */ addvar(VAR_VALUE, "battery_number", "Set number of batteries that make a pack to n (integer, 1-9)"); /* For testing purposes */ addvar(VAR_FLAG, "testing", "If invoked the driver will exec also commands that still need testing"); } /* Unskip vars according to protocol used by the UPS */ static void voltronic_massive_unskip(const int protocol) { item_t *item; for (item = voltronic_qx2nut; item->info_type != NULL; item++) { if (!item->command) continue; if ( /* == Multiphase UPSes == */ /* P09 */ (protocol == 9 && ( /* (!strcasecmp(item->info_type, "input.L1-L3.voltage") && item->from == 25) || *//* Not unskipped because P09 should be 2-phase input/output */ /* (!strcasecmp(item->info_type, "input.L2-L3.voltage") && item->from == 31) || *//* Not unskipped because P09 should be 2-phase input/output */ /* (!strcasecmp(item->info_type, "output.L1-L3.voltage") && item->from == 25) || *//* Not unskipped because P09 should be 2-phase input/output */ /* (!strcasecmp(item->info_type, "output.L2-L3.voltage") && item->from == 31) || *//* Not unskipped because P09 should be 2-phase input/output */ (!strcasecmp(item->info_type, "output.bypass.L1-N.voltage") && item->answer_len == 19) || (!strcasecmp(item->info_type, "output.bypass.L2-N.voltage") && item->answer_len == 19)/* || (!strcasecmp(item->info_type, "output.bypass.L3-N.voltage") && item->answer_len == 19) *//* Not unskipped because P09 should be 2-phase input/output */ )) || /* P10 */ (protocol == 10 && ( !strcasecmp(item->info_type, "output.L3-N.voltage") || (!strcasecmp(item->info_type, "output.L2-L3.voltage") && item->from == 25) || (!strcasecmp(item->info_type, "output.L1-L3.voltage") && item->from == 31) || (!strcasecmp(item->info_type, "output.bypass.L1-N.voltage") && item->answer_len == 37) || (!strcasecmp(item->info_type, "output.bypass.L2-N.voltage") && item->answer_len == 37) || (!strcasecmp(item->info_type, "output.bypass.L3-N.voltage") && item->answer_len == 37) || !strcasecmp(item->info_type, "output.bypass.L1-L2.voltage") || !strcasecmp(item->info_type, "output.bypass.L2-L3.voltage") || !strcasecmp(item->info_type, "output.bypass.L1-L3.voltage") || !strcasecmp(item->info_type, "output.L3.current") || !strcasecmp(item->info_type, "output.L3.power.percent") )) || /* P09-P10 */ ((protocol == 9 || protocol == 10) && ( !strcasecmp(item->info_type, "output.L1-N.voltage") || !strcasecmp(item->info_type, "output.L2-N.voltage") ||/* !strcasecmp(item->info_type, "output.L3-N.voltage") || *//* Not unskipped because P09 should be 2-phase input/output */ !strcasecmp(item->info_type, "output.L1-L2.voltage") || !strcasecmp(item->info_type, "output.L1.current") || !strcasecmp(item->info_type, "output.L2.current") ||/* !strcasecmp(item->info_type, "output.L3.current") || *//* Not unskipped because P09 should be 2-phase input/output */ !strcasecmp(item->info_type, "output.L1.power.percent") || !strcasecmp(item->info_type, "output.L2.power.percent")/* || !strcasecmp(item->info_type, "output.L3.power.percent") *//* Not unskipped because P09 should be 2-phase input/output */ )) || /* P03-P09-P10 */ ((protocol == 3 || protocol == 9 || protocol == 10) && ( !strcasecmp(item->info_type, "input.L1-N.voltage") || !strcasecmp(item->info_type, "input.L2-N.voltage") ||/* !strcasecmp(item->info_type, "input.L3-N.voltage") ||*//* Not unskipped because P09 should be 2-phase input/output */ !strcasecmp(item->info_type, "input.L1-L2.voltage") || !strcasecmp(item->info_type, "input.L1.current") || !strcasecmp(item->info_type, "input.L2.current")/* || !strcasecmp(item->info_type, "input.L3.current")*//* Not unskipped because P09 should be 2-phase input/output */ )) || /* P03-P10 */ ((protocol == 3 || protocol == 10) && ( !strcasecmp(item->info_type, "input.L3-N.voltage") || (!strcasecmp(item->info_type, "input.L2-L3.voltage") && item->from == 25) || (!strcasecmp(item->info_type, "input.L1-L3.voltage") && item->from == 31) || !strcasecmp(item->info_type, "input.L3.current") )) || /* == P31 battery type/device grid working range == */ (protocol == 31 && ( !strcasecmp(item->info_type, "battery.type") || (!strcasecmp(item->info_type, "work_range_type") && !(item->qxflags & QX_FLAG_SETVAR)) || (!strcasecmp(item->info_type, "work_range_type") && (item->qxflags & QX_FLAG_SETVAR) && getval(item->info_type)) )) || /* == ByPass limits: all but P00/P08/P31 == */ (protocol != 0 && protocol != 8 && protocol != 31 && ( (!strcasecmp(item->info_type, "max_bypass_volt") && !(item->qxflags & QX_FLAG_SETVAR)) || (!strcasecmp(item->info_type, "min_bypass_volt") && !(item->qxflags & QX_FLAG_SETVAR)) || (!strcasecmp(item->info_type, "max_bypass_freq") && !(item->qxflags & QX_FLAG_SETVAR)) || (!strcasecmp(item->info_type, "min_bypass_freq") && !(item->qxflags & QX_FLAG_SETVAR)) )) || /* == Reset capabilities options to safe default values == */ (!strcasecmp(item->info_type, "reset_to_default") && testvar("reset_to_default")) || /* == QBDR (unknown) == */ (!strcasecmp(item->info_type, "unknown.7") && testvar("testing")) ) { item->qxflags &= ~QX_FLAG_SKIP; } } } /* == Preprocess functions == */ /* *SETVAR(/NONUT)* Preprocess setvars */ static int voltronic_process_setvar(item_t *item, char *value, 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, "ups.delay.start")) { /* Truncate to minute */ val -= ((int)val % 60); snprintf(value, valuelen, "%.0f", val); return 0; } else if (!strcasecmp(item->info_type, "ups.delay.shutdown")) { /* Truncate to nearest setable value */ if (val < 60) { val -= ((int)val % 6); } else { val -= ((int)val % 60); } snprintf(value, valuelen, "%.0f", val); return 0; } else if (!strcasecmp(item->info_type, "max_bypass_freq")) { if (val == max_bypass_freq) { upslogx(LOG_INFO, "%s is already set to %.1f", item->info_type, val); return -1; } } else if (!strcasecmp(item->info_type, "min_bypass_freq")) { if (val == min_bypass_freq) { upslogx(LOG_INFO, "%s is already set to %.1f", item->info_type, val); return -1; } } else if (!strcasecmp(item->info_type, "max_bypass_volt")) { if (val == max_bypass_volt) { upslogx(LOG_INFO, "%s is already set to %.0f", item->info_type, val); return -1; } } else if (!strcasecmp(item->info_type, "min_bypass_volt")) { if (val == min_bypass_volt) { upslogx(LOG_INFO, "%s is already set to %.0f", item->info_type, val); return -1; } } else if (!strcasecmp(item->info_type, "battery_number")) { if (val == battery_number) { upslogx(LOG_INFO, "%s is already set to %.0f", item->info_type, val); return -1; } } snprintf(value, valuelen, item->command, val); return 0; } /* *CMD* Preprocess instant commands */ static int voltronic_process_command(item_t *item, char *value, size_t valuelen) { char buf[SMALLBUF] = ""; if (!strcasecmp(item->info_type, "shutdown.return")) { /* Sn: Shutdown after n minutes and then turn on when mains is back * SnRm: Shutdown after n minutes and then turn on after m minutes * Accepted values for n: .2 -> .9 , 01 -> 99 * Accepted values for m: 0001 -> 9999 */ int offdelay = strtol(dstate_getinfo("ups.delay.shutdown"), NULL, 10), ondelay = strtol(dstate_getinfo("ups.delay.start"), NULL, 10) / 60; if (ondelay == 0) { if (offdelay < 60) { snprintf(buf, sizeof(buf), ".%d", offdelay / 6); } else { snprintf(buf, sizeof(buf), "%02d", offdelay / 60); } } else if (offdelay < 60) { snprintf(buf, sizeof(buf), ".%dR%04d", offdelay / 6, ondelay); } else { snprintf(buf, sizeof(buf), "%02dR%04d", offdelay / 60, ondelay); } } else if (!strcasecmp(item->info_type, "shutdown.stayoff")) { /* SnR0000 * Shutdown after n minutes and stay off * Accepted values for n: .2 -> .9 , 01 -> 99 */ int offdelay = strtol(dstate_getinfo("ups.delay.shutdown"), NULL, 10); if (offdelay < 60) { snprintf(buf, sizeof(buf), ".%d", offdelay / 6); } else { snprintf(buf, sizeof(buf), "%02d", offdelay / 60); } } else if (!strcasecmp(item->info_type, "test.battery.start")) { /* Accepted values for test time: .2 -> .9 (.2=12sec ..), 01 -> 99 (minutes) * -> you have to invoke test.battery.start + number of seconds [12..5940] */ int delay; if (strlen(value) != strspn(value, "0123456789")) { upslogx(LOG_ERR, "%s: non numerical value [%s]", item->info_type, value); return -1; } delay = strlen(value) > 0 ? strtol(value, NULL, 10) : 600; if ((delay < 12) || (delay > 5940)) { upslogx(LOG_ERR, "%s: battery test time '%d' out of range [12..5940] seconds", item->info_type, delay); return -1; } /* test time < 1 minute */ if (delay < 60) { delay = delay / 6; snprintf(buf, sizeof(buf), ".%d", delay); /* test time > 1 minute */ } else { delay = delay / 60; snprintf(buf, sizeof(buf), "%02d", delay); } } else if (!strcasecmp(item->info_type, "beeper.toggle")) { const char *beeper_status = dstate_getinfo("ups.beeper.status"); /* If the UPS is beeping then we can call BZOFF; if we previously set BZOFF we can call BZON, provided that the beeper is not disabled */ /* The UPS can disable/enable alarm (from UPS capability) */ if (alarm_control) { if (!strcmp(beeper_status, "enabled")) { snprintf(buf, sizeof(buf), "OFF"); } else if (!strcmp(beeper_status, "muted")) { snprintf(buf, sizeof(buf), "ON"); /* Beeper disabled */ } else { upslogx(LOG_INFO, "The beeper is already disabled"); return -1; } /* The UPS can't disable/enable alarm (from UPS capability) */ } else { if (!strcmp(beeper_status, "enabled")) { snprintf(buf, sizeof(buf), "OFF"); } else if (!strcmp(beeper_status, "disabled")) { snprintf(buf, sizeof(buf), "ON"); } } } else { /* Don't know what happened */ return -1; } snprintf(value, valuelen, item->command, buf); return 0; } /* UPS capabilities */ static int voltronic_capability(item_t *item, char *value, size_t valuelen) { char rawval[SMALLBUF], *enabled, *disabled, *val = NULL, *saveptr = NULL; item_t *unskip; snprintf(rawval, sizeof(rawval), "%s", item->value); enabled = strtok_r(rawval+1, "D", &saveptr); disabled = strtok_r(NULL, "\0", &saveptr); if (!enabled && !disabled) { upsdebugx(2, "%s: parsing failed", __func__); return -1; } enabled = enabled ? enabled : ""; disabled = disabled ? disabled : ""; /* NONUT items */ if (!strcasecmp(item->info_type, "bypass_alarm")) { if (strchr(enabled, 'p')) { val = bypass_alarm = "enabled"; } else if (strchr(disabled, 'p')) { val = bypass_alarm = "disabled"; } } else if (!strcasecmp(item->info_type, "battery_alarm")) { if (strchr(enabled, 'b')) { val = battery_alarm = "enabled"; } else if (strchr(disabled, 'b')) { val = battery_alarm = "disabled"; } } else if (!strcasecmp(item->info_type, "bypass_when_off")) { if (strchr(enabled, 'o')) { val = bypass_when_off = "enabled"; } else if (strchr(disabled, 'o')) { val = bypass_when_off = "disabled"; } } else if (!strcasecmp(item->info_type, "alarm_control")) { if (strchr(item->value, 'a')) { if (strchr(enabled, 'a')) { const char *beeper = dstate_getinfo("ups.beeper.status"); val = alarm_control = "enabled"; if (!beeper || strcmp(beeper, "muted")) { dstate_setinfo("ups.beeper.status", "enabled"); } } else if (strchr(disabled, 'a')) { val = alarm_control = "disabled"; dstate_setinfo("ups.beeper.status", "disabled"); } /* Unskip beeper.{enable,disable} instcmds */ unskip = find_nut_info("beeper.enable", QX_FLAG_CMD, 0); /* Don't know what happened */ if (!unskip) return -1; unskip->qxflags &= ~QX_FLAG_SKIP; unskip = find_nut_info("beeper.disable", QX_FLAG_CMD, 0); /* Don't know what happened */ if (!unskip) return -1; unskip->qxflags &= ~QX_FLAG_SKIP; } } else if (!strcasecmp(item->info_type, "converter_mode")) { if (strchr(enabled, 'v')) { val = converter_mode = "enabled"; } else if (strchr(disabled, 'v')) { val = converter_mode = "disabled"; } } else if (!strcasecmp(item->info_type, "eco_mode")) { if (strchr(item->value, 'e')) { if (strchr(enabled, 'e')) { val = eco_mode = "enabled"; } else if (strchr(disabled, 'e')) { val = eco_mode = "disabled"; } /* Unskip bypass.{start,stop} instcmds */ unskip = find_nut_info("bypass.start", QX_FLAG_CMD, 0); /* Don't know what happened */ if (!unskip) return -1; unskip->qxflags &= ~QX_FLAG_SKIP; unskip = find_nut_info("bypass.stop", QX_FLAG_CMD, 0); /* Don't know what happened */ if (!unskip) return -1; unskip->qxflags &= ~QX_FLAG_SKIP; /* Unskip input.transfer.{high,low} */ unskip = find_nut_info("input.transfer.high", QX_FLAG_SEMI_STATIC, 0); /* Don't know what happened */ if (!unskip) return -1; unskip->qxflags &= ~QX_FLAG_SKIP; unskip = find_nut_info("input.transfer.low", QX_FLAG_SEMI_STATIC, 0); /* Don't know what happened */ if (!unskip) return -1; unskip->qxflags &= ~QX_FLAG_SKIP; /* Unskip input.frequency.{high,low} */ unskip = find_nut_info("input.frequency.high", QX_FLAG_SEMI_STATIC, 0); /* Don't know what happened */ if (!unskip) return -1; unskip->qxflags &= ~QX_FLAG_SKIP; unskip = find_nut_info("input.frequency.low", QX_FLAG_SEMI_STATIC, 0); /* Don't know what happened */ if (!unskip) return -1; unskip->qxflags &= ~QX_FLAG_SKIP; } } else if (!strcasecmp(item->info_type, "battery_open_status_check")) { if (strchr(enabled, 'd')) { val = battery_open_status_check = "enabled"; } else if (strchr(disabled, 'd')) { val = battery_open_status_check = "disabled"; } } else if (!strcasecmp(item->info_type, "bypass_forbidding")) { if (strchr(enabled, 'f')) { val = bypass_forbidding = "enabled"; } else if (strchr(disabled, 'f')) { val = bypass_forbidding = "disabled"; } } else if (!strcasecmp(item->info_type, "site_fault_detection")) { if (strchr(enabled, 'l')) { val = site_fault_detection = "enabled"; } else if (strchr(disabled, 'l')) { val = site_fault_detection = "disabled"; } } else if (!strcasecmp(item->info_type, "advanced_eco_mode")) { if (strchr(enabled, 'n')) { val = advanced_eco_mode = "enabled"; } else if (strchr(disabled, 'n')) { val = advanced_eco_mode = "disabled"; } } else if (!strcasecmp(item->info_type, "constant_phase_angle")) { if (strchr(enabled, 'q')) { val = constant_phase_angle = "enabled"; } else if (strchr(disabled, 'q')) { val = constant_phase_angle = "disabled"; } } else if (!strcasecmp(item->info_type, "limited_runtime_on_battery")) { if (strchr(enabled, 'w')) { val = limited_runtime_on_battery = "enabled"; } else if (strchr(disabled, 'w')) { val = limited_runtime_on_battery = "disabled"; } /* } else if (!strcasecmp(item->info_type, "")) { if (strchr(enabled, 'h')) { unknown/unused } else if (strchr(disabled, 'h')) { } } else if (!strcasecmp(item->info_type, "")) { if (strchr(enabled, 't')) { unknown/unused } else if (strchr(disabled, 't')) { } } else if (!strcasecmp(item->info_type, "")) { if (strchr(enabled, 'k')) { unknown/unused } else if (strchr(disabled, 'k')) { } } else if (!strcasecmp(item->info_type, "")) { if (strchr(enabled, 'i')) { unknown/unused } else if (strchr(disabled, 'i')) { } } else if (!strcasecmp(item->info_type, "")) { if (strchr(enabled, 'm')) { unknown/unused } else if (strchr(disabled, 'm')) { } } else if (!strcasecmp(item->info_type, "")) { if (strchr(enabled, 'z')) { unknown/unused } else if (strchr(disabled, 'z')) { } */ /* Items with a NUT variable */ } else if (!strcasecmp(item->info_type, "ups.start.auto")) { if (strchr(enabled, 'r')) { val = "yes"; } else if (strchr(disabled, 'r')) { val = "no"; } } else if (!strcasecmp(item->info_type, "battery.protection")) { if (strchr(enabled, 's')) { val = "yes"; } else if (strchr(disabled, 's')) { val = "no"; } } else if (!strcasecmp(item->info_type, "battery.energysave")) { if (strchr(enabled, 'g')) { val = "yes"; } else if (strchr(disabled, 'g')) { val = "no"; } } else if (!strcasecmp(item->info_type, "ups.start.battery")) { if (strchr(enabled, 'c')) { val = "yes"; } else if (strchr(disabled, 'c')) { val = "no"; } } else if (!strcasecmp(item->info_type, "outlet.0.switchable")) { if (strchr(enabled, 'j')) { int i; char buf[SMALLBUF]; val = "yes"; /* Unskip outlet.n.{switchable,status} */ for (i = 1; i <= 4; i++) { snprintf(buf, sizeof(buf), "outlet.%d.switchable", i); unskip = find_nut_info(buf, 0, 0); /* Don't know what happened */ if (!unskip) return -1; unskip->qxflags &= ~QX_FLAG_SKIP; snprintf(buf, sizeof(buf), "outlet.%d.status", i); unskip = find_nut_info(buf, 0, 0); /* Don't know what happened */ if (!unskip) return -1; unskip->qxflags &= ~QX_FLAG_SKIP; } } else if (strchr(disabled, 'j')) { val = "no"; } } /* UPS doesn't have that capability */ if (!val) return -1; snprintf(value, valuelen, item->dfl, val); /* This item doesn't have a NUT var and we were not asked by the user to change its 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; } /* *SETVAR* Set UPS capability options */ static int voltronic_capability_set(item_t *item, char *value, size_t valuelen) { if (!strcasecmp(value, "yes")) { snprintf(value, valuelen, item->command, "E"); return 0; } if (!strcasecmp(value, "no")) { snprintf(value, valuelen, item->command, "D"); return 0; } /* At this point value should have been already checked against enum so this shouldn't happen.. however.. */ upslogx(LOG_ERR, "%s: given value [%s] is not acceptable. Enter either 'yes' or 'no'.", item->info_type, value); return -1; } /* *SETVAR/NONUT* Change UPS capability according to user configuration in ups.conf */ static int voltronic_capability_set_nonut(item_t *item, char *value, size_t valuelen) { const char *match = NULL; int i; const struct { const char *type; /* Name of the option */ const char *match; /* Value reported by the UPS */ } capability[] = { { "bypass_alarm", bypass_alarm }, { "battery_alarm", battery_alarm }, { "bypass_when_off", bypass_when_off }, { "alarm_control", alarm_control }, { "converter_mode", converter_mode }, { "eco_mode", eco_mode }, { "battery_open_status_check", battery_open_status_check }, { "bypass_forbidding", bypass_forbidding }, { "site_fault_detection", site_fault_detection }, { "advanced_eco_mode", advanced_eco_mode }, { "constant_phase_angle", constant_phase_angle }, { "limited_runtime_on_battery", limited_runtime_on_battery }, { NULL } }; for (i = 0; capability[i].type; i++) { if (strcasecmp(item->info_type, capability[i].type)) continue; match = capability[i].match; break; } /* UPS doesn't have that capability */ if (!match) return -1; /* At this point value should have been already checked by nutdrv_qx's own setvar so this shouldn't happen.. however.. */ if (!strcasecmp(value, match)) { upslogx(LOG_INFO, "%s is already %s", item->info_type, match); return -1; } if (!strcasecmp(value, "disabled")) { snprintf(value, valuelen, item->command, "D"); } else if (!strcasecmp(value, "enabled")) { snprintf(value, valuelen, item->command, "E"); } else { /* At this point value should have been already checked against enum so this shouldn't happen.. however.. */ upslogx(LOG_ERR, "%s: [%s] is not within acceptable values [enabled/disabled]", item->info_type, value); return -1; } return 0; } /* *SETVAR/NONUT* Reset capability options and their limits to safe default values */ static int voltronic_capability_reset(item_t *item, char *value, size_t valuelen) { /* Nothing to do */ if (!testvar("reset_to_default")) return -1; /* UPS capability options can be reset only when the UPS is in 'Standby Mode' (=OFF) (from QMOD) */ if (!(qx_status() & STATUS(OFF))) { upslogx(LOG_ERR, "%s: UPS capability options can be reset only when the UPS is in Standby Mode (i.e. ups.status = 'OFF').", item->info_type); return -1; } snprintf(value, valuelen, "%s", item->command); return 0; } /* Voltage limits for ECO Mode */ static int voltronic_eco_volt(item_t *item, char *value, size_t valuelen) { const int protocol = strtol(dstate_getinfo("ups.firmware.aux")+1, NULL, 10); int ovn; const char *outvoltnom; char buf[SMALLBUF]; item_t *unskip; /* Range of accepted values for maximum voltage for ECO Mode */ struct { int lower; /* Lower limit */ int upper; /* Upper limit */ } max; /* Range of accepted values for minimum voltage for ECO Mode */ struct { int lower; /* Lower limit */ int upper; /* Upper limit */ } min; if (strspn(item->value, "0123456789 .") != strlen(item->value)) { upsdebugx(2, "%s: non numerical value [%s: %s]", __func__, item->info_type, item->value); return -1; } snprintf(value, valuelen, item->dfl, strtod(item->value, NULL)); outvoltnom = dstate_getinfo("output.voltage.nominal"); /* Since query for ratings (QRI) is not mandatory to run this driver, skip next steps if we can't get the value of output voltage nominal */ if (!outvoltnom) { upsdebugx(2, "%s: unable to get output voltage nominal", __func__); /* We return 0 since we have the value and all's ok: simply we can't set its range so we won't unskip SETVAR item and .{min,max} */ return 0; } ovn = strtol(outvoltnom, NULL, 10); /* For P01/P09 */ if (protocol == 1 || protocol == 9) { if (ovn >= 200) { min.lower = ovn - 24; min.upper = ovn - 7; max.lower = ovn + 7; max.upper = ovn + 24; } else { min.lower = ovn - 12; min.upper = ovn - 3; max.lower = ovn + 3; max.upper = ovn + 12; } /* For P02/P03/P10/P13/P14/P99 */ } else if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99) { if (ovn >= 200) { min.lower = ovn - 24; min.upper = ovn - 11; max.lower = ovn + 11; max.upper = ovn + 24; } else { min.lower = ovn - 12; min.upper = ovn - 5; max.lower = ovn + 5; max.upper = ovn + 12; } /* ECO mode not supported */ } else { upsdebugx(2, "%s: the UPS doesn't seem to support ECO Mode", __func__); /* We return 0 since we have the value and all's ok: simply we can't set its range so we won't unskip SETVAR item and .{min,max} */ return 0; } if (!strcasecmp(item->info_type, "input.transfer.high")) { /* Fill voltronic_r_eco_volt_max */ snprintf(item->info_rw[0].value, sizeof(item->info_rw[0].value), "%d", max.lower); snprintf(item->info_rw[1].value, sizeof(item->info_rw[1].value), "%d", max.upper); } else if (!strcasecmp(item->info_type, "input.transfer.low")) { /* Fill voltronic_r_eco_volt_min */ snprintf(item->info_rw[0].value, sizeof(item->info_rw[0].value), "%d", min.lower); snprintf(item->info_rw[1].value, sizeof(item->info_rw[1].value), "%d", min.upper); } /* Unskip input.transfer.{high,low}.{min,max} */ snprintf(buf, sizeof(buf), "%s.min", item->info_type); unskip = find_nut_info(buf, QX_FLAG_SEMI_STATIC, 0); /* Don't know what happened */ if (!unskip) return -1; unskip->qxflags &= ~QX_FLAG_SKIP; snprintf(buf, sizeof(buf), "%s.max", item->info_type); unskip = find_nut_info(buf, QX_FLAG_SEMI_STATIC, 0); /* Don't know what happened */ if (!unskip) return -1; unskip->qxflags &= ~QX_FLAG_SKIP; /* Unskip input.transfer.{high,low} 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 limits for ECO Mode (max, min) */ static int voltronic_eco_volt_range(item_t *item, char *value, size_t valuelen) { char *buf; int i; item_t *from; if (!strcasecmp(item->info_type, "input.transfer.low.min")) { buf = "input.transfer.low"; i = 0; } else if (!strcasecmp(item->info_type, "input.transfer.low.max")) { buf = "input.transfer.low"; i = 1; } else if (!strcasecmp(item->info_type, "input.transfer.high.min")) { buf = "input.transfer.high"; i = 0; } else if (!strcasecmp(item->info_type, "input.transfer.high.max")) { buf = "input.transfer.high"; i = 1; } else { /* Don't know what happened */ return -1; } from = find_nut_info(buf, QX_FLAG_SEMI_STATIC, 0); /* Don't know what happened */ if (!from) return -1; /* Value is set at runtime by voltronic_eco_volt, so if it's still unset something went wrong */ if (!strlen(from->info_rw[i].value)) return -1; snprintf(value, valuelen, "%s", from->info_rw[i].value); return 0; } /* Frequency limits for ECO Mode */ static int voltronic_eco_freq(item_t *item, char *value, 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; } snprintf(value, valuelen, item->dfl, strtod(item->value, NULL)); /* Unskip input.transfer.{high,low} 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; } /* *NONUT* Voltage/frequency limits for Bypass Mode */ static int voltronic_bypass(item_t *item, char *value, size_t valuelen) { item_t *unskip; double val; if (strspn(item->value, "0123456789 .") != strlen(item->value)) { upsdebugx(2, "%s: non numerical value [%s: %s]", __func__, item->info_type, item->value); return -1; } if (!strcasecmp(item->info_type, "max_bypass_volt")) { val = max_bypass_volt = strtol(item->value, NULL, 10); } else if (!strcasecmp(item->info_type, "min_bypass_volt")) { val = min_bypass_volt = strtol(item->value, NULL, 10); } else if (!strcasecmp(item->info_type, "max_bypass_freq")) { val = max_bypass_freq = strtod(item->value, NULL); } else if (!strcasecmp(item->info_type, "min_bypass_freq")) { val = min_bypass_freq = strtod(item->value, NULL); } else { /* Don't know what happened */ return -1; } snprintf(value, valuelen, item->dfl, val); /* No user-provided value to change.. */ if (!getval(item->info_type)) return 0; /* Unskip {min,max}_bypass_volt 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; } /* *NONUT* Number of batteries */ static int voltronic_batt_numb(item_t *item, char *value, 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; } battery_number = strtol(item->value, NULL, 10); snprintf(value, valuelen, item->dfl, battery_number); /* No user-provided value to change.. */ if (!getval(item->info_type)) return 0; /* Unskip battery_number setvar */ unskip = find_nut_info("battery_number", QX_FLAG_SETVAR, 0); /* Don't know what happened */ if (!unskip) return -1; unskip->qxflags &= ~QX_FLAG_SKIP; return 0; } /* Battery runtime */ static int voltronic_batt_runtime(item_t *item, char *value, 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; snprintf(value, valuelen, item->dfl, runtime); return 0; } /* Protocol used by the UPS */ static int voltronic_protocol(item_t *item, char *value, size_t valuelen) { int protocol; if (strncasecmp(item->value, "PI", 2)) { upsdebugx(2, "%s: invalid start characters [%.2s]", __func__, item->value); return -1; } /* Here we exclude non numerical value and other non accepted protocols (hence the restricted comparison target) */ if (strspn(item->value+2, "0123489") != strlen(item->value+2)) { upslogx(LOG_ERR, "Protocol [%s] is not supported by this driver", item->value); return -1; } protocol = strtol(item->value+2, NULL, 10); switch (protocol) { case 0: case 1: case 2: case 3: case 8: case 9: case 10: case 13: case 14: case 31: case 99: break; default: upslogx(LOG_ERR, "Protocol [PI%02d] is not supported by this driver", protocol); return -1; } snprintf(value, valuelen, "P%02d", protocol); /* Unskip vars according to protocol */ voltronic_massive_unskip(protocol); return 0; } /* Fault reported by the UPS: * When the UPS is queried for status (QGS), if it reports a fault (6th bit of 12bit flag of the reply to QGS set to 1), the driver unskips the QFS item in qx2nut array: this function processes the reply to QFS query */ static int voltronic_fault(item_t *item, char *value, size_t valuelen) { int protocol = strtol(dstate_getinfo("ups.firmware.aux")+1, NULL, 10); char alarm[SMALLBUF]; upslogx(LOG_INFO, "Checking for faults.."); if (!strcasecmp(item->value, "OK")) { snprintf(value, valuelen, item->dfl, "No fault found"); upslogx(LOG_INFO, "%s", value); item->qxflags |= QX_FLAG_SKIP; return 0; } if ((strspn(item->value, "0123456789ABC") != 2) || ((item->value[0] != '1') && (strspn(item->value+1, "0123456789") != 1))) { snprintf(alarm, sizeof(alarm), "Unknown fault [%s]", item->value); /* P31 UPSes */ } else if (protocol == 31) { if (strpbrk(item->value+1, "ABC")) { snprintf(alarm, sizeof(alarm), "Unknown fault [%s]", item->value); } else { switch (strtol(item->value, NULL, 10)) { case 1: strcpy(alarm, "Fan failure."); break; case 2: strcpy(alarm, "Over temperature fault."); break; case 3: strcpy(alarm, "Battery voltage is too high."); break; case 4: strcpy(alarm, "Battery voltage too low."); break; case 5: strcpy(alarm, "Inverter relay short-circuited."); break; case 6: strcpy(alarm, "Inverter voltage over maximum value."); break; case 7: strcpy(alarm, "Overload fault."); update_status("OVER"); break; case 8: strcpy(alarm, "Bus voltage exceeds its upper limit."); break; case 9: strcpy(alarm, "Bus soft start fail."); break; case 10: strcpy(alarm, "Unknown fault [Fault code: 10]"); break; case 51: strcpy(alarm, "Over current fault."); break; case 52: strcpy(alarm, "Bus voltage below its under limit."); break; case 53: strcpy(alarm, "Inverter soft start fail."); break; case 54: strcpy(alarm, "Self test fail."); break; case 55: strcpy(alarm, "Output DC voltage exceeds its upper limit."); break; case 56: strcpy(alarm, "Battery open fault."); break; case 57: strcpy(alarm, "Current sensor fault."); break; case 58: strcpy(alarm, "Battery short."); break; case 59: strcpy(alarm, "Inverter voltage below its lower limit."); break; default: snprintf(alarm, sizeof(alarm), "Unknown fault [%s]", item->value); break; } } /* All other UPSes */ } else { switch (strtol(item->value, NULL, 10)) { case 1: switch (item->value[1]) { case 'A': strcpy(alarm, "L1 inverter negative power out of acceptable range."); break; case 'B': strcpy(alarm, "L2 inverter negative power out of acceptable range."); break; case 'C': strcpy(alarm, "L3 inverter negative power out of acceptable range."); break; default: strcpy(alarm, "Bus voltage not within default setting."); break; } break; case 2: strcpy(alarm, "Bus voltage over maximum value."); break; case 3: strcpy(alarm, "Bus voltage below minimum value."); break; case 4: strcpy(alarm, "Bus voltage differences out of acceptable range."); break; case 5: strcpy(alarm, "Bus voltage of slope rate drops too fast."); break; case 6: strcpy(alarm, "Over current in PFC input inductor."); break; case 11: strcpy(alarm, "Inverter voltage not within default setting."); break; case 12: strcpy(alarm, "Inverter voltage over maximum value."); break; case 13: strcpy(alarm, "Inverter voltage below minimum value."); break; case 14: strcpy(alarm, "Inverter short-circuited."); break; case 15: strcpy(alarm, "L2 phase inverter short-circuited."); break; case 16: strcpy(alarm, "L3 phase inverter short-circuited."); break; case 17: strcpy(alarm, "L1L2 inverter short-circuited."); break; case 18: strcpy(alarm, "L2L3 inverter short-circuited."); break; case 19: strcpy(alarm, "L3L1 inverter short-circuited."); break; case 21: strcpy(alarm, "Battery SCR short-circuited."); break; case 22: strcpy(alarm, "Line SCR short-circuited."); break; case 23: strcpy(alarm, "Inverter relay open fault."); break; case 24: strcpy(alarm, "Inverter relay short-circuited."); break; case 25: strcpy(alarm, "Input and output wires oppositely connected."); break; case 26: strcpy(alarm, "Battery oppositely connected."); break; case 27: strcpy(alarm, "Battery voltage is too high."); break; case 28: strcpy(alarm, "Battery voltage too low."); break; case 29: strcpy(alarm, "Failure for battery fuse being open-circuited."); break; case 31: strcpy(alarm, "CAN-bus communication fault."); break; case 32: strcpy(alarm, "Host signal circuit fault."); break; case 33: strcpy(alarm, "Synchronous signal circuit fault."); break; case 34: strcpy(alarm, "Synchronous pulse signal circuit fault."); break; case 35: strcpy(alarm, "Parallel cable disconnected."); break; case 36: strcpy(alarm, "Load unbalanced."); break; case 41: strcpy(alarm, "Over temperature fault."); break; case 42: strcpy(alarm, "Communication failure between CPUs in control board."); break; case 43: strcpy(alarm, "Overload fault."); update_status("OVER"); break; case 44: strcpy(alarm, "Fan failure."); break; case 45: strcpy(alarm, "Charger failure."); break; case 46: strcpy(alarm, "Model fault."); break; case 47: strcpy(alarm, "MCU communication fault."); break; default: snprintf(alarm, sizeof(alarm), "Unknown fault [%s]", item->value); break; } } snprintf(value, valuelen, item->dfl, alarm); upslogx(LOG_INFO, "Fault found: %s", alarm); item->qxflags |= QX_FLAG_SKIP; return 0; } /* Warnings reported by the UPS */ static int voltronic_warning(item_t *item, char *value, size_t valuelen) { char warn[SMALLBUF] = "", unk[SMALLBUF] = "", bitwarns[SMALLBUF] = "", warns[4096] = ""; int i; if (strspn(item->value, "01") != strlen(item->value)) { upsdebugx(2, "%s: invalid reply from the UPS [%s]", __func__, item->value); return -1; } /* No warnings */ if (strspn(item->value, "0") == strlen(item->value)) { return 0; } snprintf(value, valuelen, "UPS warnings:"); for (i = 0; i < (int)strlen(item->value); i++) { int u = 0; if (item->value[i] == '1') { switch (i) { case 0: strcpy(warn, "Battery disconnected."); break; case 1: strcpy(warn, "Neutral not connected."); break; case 2: strcpy(warn, "Site fault."); break; case 3: strcpy(warn, "Phase sequence incorrect."); break; case 4: strcpy(warn, "Phase sequence incorrect in bypass."); break; case 5: strcpy(warn, "Input frequency unstable in bypass."); break; case 6: strcpy(warn, "Battery overcharged."); break; case 7: strcpy(warn, "Low battery."); update_status("LB"); break; case 8: strcpy(warn, "Overload alarm."); update_status("OVER"); break; case 9: strcpy(warn, "Fan alarm."); break; case 10: strcpy(warn, "EPO enabled."); break; case 11: strcpy(warn, "Unable to turn on UPS."); break; case 12: strcpy(warn, "Over temperature alarm."); break; case 13: strcpy(warn, "Charger alarm."); break; case 14: strcpy(warn, "Remote auto shutdown."); break; case 15: strcpy(warn, "L1 input fuse not working."); break; case 16: strcpy(warn, "L2 input fuse not working."); break; case 17: strcpy(warn, "L3 input fuse not working."); break; case 18: strcpy(warn, "Positive PFC abnormal in L1."); break; case 19: strcpy(warn, "Negative PFC abnormal in L1."); break; case 20: strcpy(warn, "Positive PFC abnormal in L2."); break; case 21: strcpy(warn, "Negative PFC abnormal in L2."); break; case 22: strcpy(warn, "Positive PFC abnormal in L3."); break; case 23: strcpy(warn, "Negative PFC abnormal in L3."); break; case 24: strcpy(warn, "Abnormal in CAN-bus communication."); break; case 25: strcpy(warn, "Abnormal in synchronous signal circuit."); break; case 26: strcpy(warn, "Abnormal in synchronous pulse signal circuit."); break; case 27: strcpy(warn, "Abnormal in host signal circuit."); break; case 28: strcpy(warn, "Male connector of parallel cable not connected well."); break; case 29: strcpy(warn, "Female connector of parallel cable not connected well."); break; case 30: strcpy(warn, "Parallel cable not connected well."); break; case 31: strcpy(warn, "Battery connection not consistent in parallel systems."); break; case 32: strcpy(warn, "AC connection not consistent in parallel systems."); break; case 33: strcpy(warn, "Bypass connection not consistent in parallel systems."); break; case 34: strcpy(warn, "UPS model types not consistent in parallel systems."); break; case 35: strcpy(warn, "Capacity of UPSes not consistent in parallel systems."); break; case 36: strcpy(warn, "Auto restart setting not consistent in parallel systems."); break; case 37: strcpy(warn, "Battery cell over charge."); break; case 38: strcpy(warn, "Battery protection setting not consistent in parallel systems."); break; case 39: strcpy(warn, "Battery detection setting not consistent in parallel systems."); break; case 40: strcpy(warn, "Bypass not allowed setting not consistent in parallel systems."); break; case 41: strcpy(warn, "Converter setting not consistent in parallel systems."); break; case 42: strcpy(warn, "High loss point for frequency in bypass mode not consistent in parallel systems."); break; case 43: strcpy(warn, "Low loss point for frequency in bypass mode not consistent in parallel systems."); break; case 44: strcpy(warn, "High loss point for voltage in bypass mode not consistent in parallel systems."); break; case 45: strcpy(warn, "Low loss point for voltage in bypass mode not consistent in parallel systems."); break; case 46: strcpy(warn, "High loss point for frequency in AC mode not consistent in parallel systems."); break; case 47: strcpy(warn, "Low loss point for frequency in AC mode not consistent in parallel systems."); break; case 48: strcpy(warn, "High loss point for voltage in AC mode not consistent in parallel systems."); break; case 49: strcpy(warn, "Low loss point for voltage in AC mode not consistent in parallel systems."); break; case 50: strcpy(warn, "Warning for locking in bypass mode after 3 consecutive overloads within 30 min."); break; case 51: strcpy(warn, "Warning for three-phase AC input current unbalance."); break; case 52: strcpy(warn, "Warning for a three-phase input current unbalance detected in battery mode."); break; case 53: strcpy(warn, "Warning for Inverter inter-current unbalance."); break; case 54: strcpy(warn, "Programmable outlets cut off pre-alarm."); break; case 55: strcpy(warn, "Warning for Battery replace."); update_status("RB"); break; case 56: strcpy(warn, "Abnormal warning on input phase angle."); break; case 57: strcpy(warn, "Warning!! Cover of maintain switch is open."); break; case 61: strcpy(warn, "EEPROM operation error."); break; default: snprintf(warn, sizeof(warn), "Unknown warning from UPS [bit: #%02d]", i + 1); u++; break; } upslogx(LOG_INFO, "Warning from UPS: %s", warn); if (u) { /* Unknown warnings */ snprintfcat(unk, sizeof(unk), ", #%02d", i + 1); } else { /* Known warnings */ if (strlen(warns) > 0) { /* For too long warnings (total) */ snprintfcat(bitwarns, sizeof(bitwarns), ", #%02d", i + 1); /* For warnings (total) not too long */ snprintfcat(warns, sizeof(warns), " %s", warn); } else { snprintf(bitwarns, sizeof(bitwarns), "Known (see log or manual) [bit: #%02d", i + 1); snprintf(warns, sizeof(warns), "%s", warn); } } } } /* There's some known warning, at least */ if (strlen(warns) > 0) { /* We have both known and unknown warnings */ if (strlen(unk) > 0) { /* Appending unknown ones to known ones; removing leading comma from unk - 'explicit' */ snprintfcat(warns, sizeof(warns), " Unknown warnings [bit:%s]", unk+1); /* Appending unknown ones to known ones; removing leading comma from unk - 'cryptic' */ snprintfcat(bitwarns, sizeof(bitwarns), "]; Unknown warnings [bit:%s]", unk+1); /* We have only known warnings */ } else { snprintfcat(bitwarns, sizeof(bitwarns), "]"); } /* We have only unknown warnings */ } else if (strlen(unk) > 0) { /* Removing leading comma from unk */ snprintf(warns, sizeof(warns), "Unknown warnings [bit:%s]", unk+1); strcpy(bitwarns, warns); } else { /* Don't know what happened */ upsdebugx(2, "%s: failed to process warnings", __func__); return -1; } /* If grand total of warnings doesn't exceed value of alarm (=ST_MAX_VALUE_LEN) minus some space (32) for other alarms.. */ if ((ST_MAX_VALUE_LEN - 32) > strlen(warns)) { /* ..then be explicit.. */ snprintfcat(value, valuelen, " %s", warns); /* ..otherwise.. */ } else { /* ..be cryptic */ snprintfcat(value, valuelen, " %s", bitwarns); } return 0; } /* Working mode reported by the UPS */ static int voltronic_mode(item_t *item, char *value, size_t valuelen) { char *status = NULL, *alarm = NULL; switch (item->value[0]) { case 'P': alarm = "UPS is going ON"; break; case 'S': status = "OFF"; break; case 'Y': status = "BYPASS"; break; case 'L': status = "OL"; break; case 'B': status = "!OL"; break; case 'T': status = "CAL"; break; case 'F': alarm = "Fault reported by UPS."; break; case 'E': alarm = "UPS is in ECO Mode."; break; case 'C': alarm = "UPS is in Converter Mode."; break; case 'D': alarm = "UPS is shutting down!"; status = "FSD"; break; default: upsdebugx(2, "%s: invalid reply from the UPS [%s]", __func__, item->value); return -1; } if (alarm && !strcasecmp(item->info_type, "ups.alarm")) { snprintf(value, valuelen, item->dfl, alarm); } else if (status && !strcasecmp(item->info_type, "ups.status")) { snprintf(value, valuelen, item->dfl, status); } return 0; } /* Process status bits */ static int voltronic_status(item_t *item, char *value, size_t valuelen) { char *val = ""; if (strspn(item->value, "01") != strlen(item->value)) { upsdebugx(3, "%s: unexpected value %s@%d->%s", __func__, item->value, item->from, item->value); return -1; } switch (item->from) { case 63: /* UPS Type - ups.type */ { int type = strtol(item->value, NULL, 10); if (!type) /* 00 -> Offline */ val = "offline"; else if (type == 1) /* 01 -> Line-interactive */ val = "line-interactive"; else if (type == 10) /* 10 -> Online */ val = "online"; else { upsdebugx(2, "%s: invalid type [%s: %s]", __func__, item->info_type, item->value); return -1; } } break; case 65: /* Utility Fail (Immediate) - ups.status */ if (item->value[0] == '1') val = "!OL"; else val = "OL"; break; case 66: /* Battery Low - ups.status */ if (item->value[0] == '1') val = "LB"; else val = "!LB"; break; case 67: /* Bypass/Boost or Buck Active - ups.{status,alarm} */ if (item->value[0] == '1') { double vi, vo; vi = strtod(dstate_getinfo("input.voltage"), NULL); vo = strtod(dstate_getinfo("output.voltage"), NULL); if (vo < 0.5 * vi) { upsdebugx(2, "%s: output voltage too low", __func__); return -1; } if (vo < 0.95 * vi) { val = "TRIM"; } else if (vo < 1.05 * vi) { int prot = strtol(dstate_getinfo("ups.firmware.aux")+1, NULL, 10); if (!prot || prot == 8) { /* ups.alarm */ if (!strcasecmp(item->info_type, "ups.alarm")) val = "UPS is in AVR Mode."; } else { /* ups.status */ if (!strcasecmp(item->info_type, "ups.status")) val = "BYPASS"; } } else if (vo < 1.5 * vi) { val = "BOOST"; } else { upsdebugx(2, "%s: output voltage too high", __func__); return -1; } } break; case 68: /* UPS Fault - ups.alarm */ if (item->value[0] == 1) { item_t *faultitem; for (faultitem = voltronic_qx2nut; faultitem->info_type != NULL; faultitem++) { if (!faultitem->command) continue; if (!strcasecmp(faultitem->command, "QFS\r")) { faultitem->qxflags &= ~QX_FLAG_SKIP; break; } } val = "UPS Fault!"; } break; /* case 69: *//* unknown */ /* break;*/ case 70: /* Test in Progress - ups.status */ if (item->value[0] == '1') val = "CAL"; else val = "!CAL"; break; case 71: /* Shutdown Active - ups.status */ if (item->value[0] == '1') val = "FSD"; else val = "!FSD"; break; case 72: /* Beeper status - ups.beeper.status */ /* The UPS has the ability to enable/disable the alarm (from UPS capability) */ if (alarm_control) { const char *beeper = dstate_getinfo("ups.beeper.status"); if (!beeper || strcasecmp(beeper, "disabled")) { if (item->value[0] == '0') /* Beeper On */ val = "enabled"; else val = "muted"; } /* The UPS lacks the ability to enable/disable the alarm (from UPS capability) */ } else { if (item->value[0] == '0') /* Beeper On */ val = "enabled"; else val = "disabled"; } break; /* case 73: *//* unknown */ /* break;*/ /* case 74: *//* unknown */ /* break;*/ default: /* Don't know what happened */ return -1; } snprintf(value, valuelen, "%s", val); return 0; } /* Output power factor */ static int voltronic_output_powerfactor(item_t *item, char *value, size_t valuelen) { double opf; if (strspn(item->value, "0123456789 .") != strlen(item->value)) { upsdebugx(2, "%s: non numerical value [%s: %s]", __func__, item->info_type, item->value); return -1; } /* UPS report a value expressed in % so -> output.powerfactor*100 e.g. opf = 0,8 -> ups = 80 */ opf = strtod(item->value, NULL) * 0.01; snprintf(value, valuelen, item->dfl, opf); return 0; } /* UPS serial number */ static int voltronic_serial_numb(item_t *item, char *value, size_t valuelen) { /* If the UPS report a 00..0 serial we'll log it but we won't store it in device.serial */ if (strspn(item->value, "0") == strlen(item->value)) { upslogx(LOG_INFO, "%s: UPS reported a non-significant serial [%s]", item->info_type, item->value); return -1; } snprintf(value, valuelen, item->dfl, item->value); return 0; } /* Outlet status */ static int voltronic_outlet(item_t *item, char *value, size_t valuelen) { const char *status, *switchable; char number = item->info_type[7], buf[SMALLBUF]; item_t *outlet_item; switch (item->value[0]) { case '1': switchable = "yes"; status = "on"; break; case '0': switchable = "yes"; status = "off"; break; default: upsdebugx(2, "%s: invalid reply from the UPS [%s: %s]", __func__, item->info_type, item->value); return -1; } if (strstr(item->info_type, "switchable")) { snprintf(value, valuelen, item->dfl, switchable); } else if (strstr(item->info_type, "status")) { snprintf(value, valuelen, item->dfl, status); } else { /* Don't know what happened */ return -1; } /* Unskip outlet.n.delay.shutdown */ snprintf(buf, sizeof(buf), "outlet.%c.delay.shutdown", number); outlet_item = find_nut_info(buf, QX_FLAG_SEMI_STATIC, 0); /* Don't know what happened*/ if (!outlet_item) return -1; outlet_item->qxflags &= ~QX_FLAG_SKIP; /* Unskip outlet.n.load.on */ snprintf(buf, sizeof(buf), "outlet.%c.load.on", number); outlet_item = find_nut_info(buf, QX_FLAG_CMD, 0); /* Don't know what happened*/ if (!outlet_item) return -1; outlet_item->qxflags &= ~QX_FLAG_SKIP; /* Unskip outlet.n.load.off */ snprintf(buf, sizeof(buf), "outlet.%c.load.off", number); outlet_item = find_nut_info(buf, QX_FLAG_CMD, 0); /* Don't know what happened*/ if (!outlet_item) return -1; outlet_item->qxflags &= ~QX_FLAG_SKIP; return 0; } /* Outlet delay time */ static int voltronic_outlet_delay(item_t *item, char *value, size_t valuelen) { char number = item->info_type[7], buf[SMALLBUF]; double val; item_t *setvar_item; if (strspn(item->value, "0123456789 .") != strlen(item->value)) { upsdebugx(2, "%s: non numerical value [%s: %s]", __func__, item->info_type, item->value); return -1; } /* UPS reports minutes, NUT expects seconds */ val = strtod(item->value, NULL) * 60; snprintf(value, valuelen, item->dfl, val); /* Unskip outlet.n.delay.shutdown setvar */ snprintf(buf, sizeof(buf), "outlet.%c.delay.shutdown", number); setvar_item = find_nut_info(buf, QX_FLAG_SETVAR, 0); /* Don't know what happened*/ if (!setvar_item) return -1; setvar_item->qxflags &= ~QX_FLAG_SKIP; return 0; } /* *SETVAR* Outlet delay time */ static int voltronic_outlet_delay_set(item_t *item, char *value, size_t valuelen) { int delay = strtol(value, NULL, 10); /* From seconds to minute */ delay = delay / 60; snprintf(value, valuelen, item->command, delay); return 0; } /* Type of battery */ static int voltronic_p31b(item_t *item, char *value, size_t valuelen) { int val; if ((item->value[0] != '0') || (strspn(item->value+1, "012") != 1)) { upsdebugx(2, "%s: invalid battery type reported by the UPS [%s]", __func__, item->value); return -1; } val = strtol(item->value, NULL, 10); snprintf(value, valuelen, item->dfl, item->info_rw[val].value); return 0; } /* *SETVAR* Type of battery */ static int voltronic_p31b_set(item_t *item, char *value, size_t valuelen) { int i; for (i = 0; strlen(item->info_rw[i].value) > 0; i++) { if (!strcasecmp(item->info_rw[i].value, value)) break; } /* At this point value should already be checked against enum so this shouldn't happen.. however.. */ if (i >= (int)(sizeof(item->info_rw) / sizeof(item->info_rw[0]))) { upslogx(LOG_ERR, "%s: value [%s] out of range", item->info_type, value); return -1; } snprintf(value, valuelen, "%d", i); return voltronic_process_setvar(item, value, valuelen); } /* *NONUT* Actual device grid working range type for P31 UPSes */ static int voltronic_p31g(item_t *item, char *value, size_t valuelen) { int val; if ((item->value[0] != '0') || (strspn(item->value+1, "01") != 1)) { upsdebugx(2, "%s: invalid device grid working range reported by the UPS [%s]", __func__, item->value); return -1; } val = strtol(item->value, NULL, 10); snprintf(value, valuelen, item->dfl, item->info_rw[val].value); work_range_type = val; return 0; } /* *SETVAR/NONUT* Device grid working range type for P31 UPSes */ static int voltronic_p31g_set(item_t *item, char *value, size_t valuelen) { int i; for (i = 0; strlen(item->info_rw[i].value) > 0; i++) { if (!strcasecmp(item->info_rw[i].value, value)) break; } /* At this point value should have been already checked against enum so this shouldn't happen.. however.. */ if (i >= (int)(sizeof(item->info_rw) / sizeof(item->info_rw[0]))) { upslogx(LOG_ERR, "%s: value [%s] out of range", item->info_type, value); return -1; } if (i == work_range_type) { upslogx(LOG_INFO, "%s is already set to %s", item->info_type, item->info_rw[i].value); return -1; } snprintf(value, valuelen, "%d", i); return voltronic_process_setvar(item, value, valuelen); } /* *NONUT* UPS actual input/output phase angles */ static int voltronic_phase(item_t *item, char *value, size_t valuelen) { int angle; if (strspn(item->value, "0123456789 .") != strlen(item->value)) { upsdebugx(2, "%s: non numerical value [%s: %s]", __func__, item->info_type, item->value); return -1; } angle = strtol(item->value, NULL, 10); if (!strcasecmp(item->info_type, "output_phase_angle")) { output_phase_angle = angle; /* User-provided value to change.. */ if (getval(item->info_type)) { item_t *unskip; /* Unskip output_phase_angle 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; } } snprintf(value, valuelen, item->dfl, angle); return 0; } /* *SETVAR/NONUT* Output phase angle */ static int voltronic_phase_set(item_t *item, char *value, size_t valuelen) { int i; for (i = 0; strlen(item->info_rw[i].value) > 0; i++) { if (!strcasecmp(item->info_rw[i].value, value)) break; } /* At this point value should have been already checked against enum so this shouldn't happen.. however.. */ if (i >= (int)(sizeof(item->info_rw) / sizeof(item->info_rw[0]))) { upslogx(LOG_ERR, "%s: value [%s] out of range", item->info_type, value); return -1; } if (strtol(item->info_rw[i].value, NULL, 10) == output_phase_angle) { upslogx(LOG_INFO, "%s is already set to %s", item->info_type, item->info_rw[i].value); return -1; } snprintf(value, valuelen, "%d", i); return voltronic_process_setvar(item, value, valuelen); } /* *NONUT* UPS is master/slave in a system of UPSes in parallel */ static int voltronic_parallel(item_t *item, char *value, size_t valuelen) { char *type; if (strlen(item->value) != strspn(item->value, "0123456789")) { upsdebugx(2, "%s: non numerical value [%s: %s]", __func__, item->info_type, item->value); return -1; } /* 001 for master UPS, 002 and 003 for slave UPSes */ switch (strtol(item->value, NULL, 10)) { case 1: type = "master"; break; case 2: case 3: type = "slave"; break; default: upsdebugx(2, "%s: invalid reply from the UPS [%s]", __func__, item->value); return -1; } snprintf(value, valuelen, "This UPS is *%s* in a system of UPSes in parallel", type); return 0; } /* == Subdriver interface == */ subdriver_t voltronic_subdriver = { VOLTRONIC_VERSION, voltronic_claim, voltronic_qx2nut, NULL, NULL, voltronic_makevartable, "ACK", "(NAK\r", #ifdef TESTING voltronic_testing, #endif /* TESTING */ };