/* microsol-apc.c - APC Back-UPS BR series UPS driver Copyright (C) 2004 Silvino B. Magalhães 2019 Roberto Panerai Velloso 2021 Ygor A. S. Regados 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 2021/03/19 - Version 0.70 - Initial release, based on solis driver */ #include "config.h" /* must be first */ #include #include #include "main.h" #include "serial.h" #include "nut_float.h" #include "timehead.h" #include "microsol-common.h" #include "microsol-apc.h" #define DRIVER_NAME "APC Back-UPS BR series UPS driver" #define DRIVER_VERSION "0.69" /* driver description structure */ upsdrv_info_t upsdrv_info = { DRIVER_NAME, DRIVER_VERSION, "Silvino B. Magalhães " "Roberto Panerai Velloso " "Ygor A. S. Regados ", DRV_STABLE, { NULL } }; #define false 0 #define true 1 #define RESP_END 0xFE #define ENDCHAR 13 /* replies end with CR */ /* solis commands */ #define CMD_UPSCONT 0xCC #define CMD_SHUT 0xDD #define CMD_SHUTRET 0xDE #define CMD_EVENT 0xCE #define CMD_DUMP 0xCD /** Check if UPS model is available here. */ bool_t ups_model_defined(void) { unsigned int model_index; for (model_index = 0; MODELS[model_index] != ups_model; model_index++); if (model_index == MODEL_COUNT) { return 0; } else { return 1; } } /** Set UPS model name. */ void set_ups_model(void) { switch (ups_model) { case 183: model_name = "BZ2200I-BR"; break; case 190: model_name = "BZ1500-BR"; break; case 191: model_name = "BZ2200BI-BR"; break; default: model_name = "Unknown UPS"; } } /** * Parse received packet with UPS instantaneous data. * This function parses model-specific values, such as voltage and battery times. */ void scan_received_pack_model_specific(void) { unsigned int relay_state; unsigned int model_index; float real_power_curve_1, real_power_curve_2, real_power_curve_3; float power_difference_1, power_difference_2, power_difference_3; bool_t recharging; /* Extract unprocessed data from packet */ input_voltage = received_packet[2]; output_voltage = received_packet[1]; output_current = received_packet[5]; battery_voltage = received_packet[3]; relay_state = (received_packet[6] & 0x28) >> 3; /* Find array indexes for detected UPS model */ for (model_index = 0; MODELS[model_index] != ups_model && model_index < MODEL_COUNT - 1; model_index++); if (MODELS[model_index] != ups_model) { upslogx(LOG_NOTICE, "UPS model not found, using fallback option."); } /* Start processing according to model */ nominal_power = NOMINAL_POWER[model_index]; input_voltage = INPUT_VOLTAGE_MULTIPLIER_A[model_index][output_220v] * input_voltage + INPUT_VOLTAGE_MULTIPLIER_B[model_index][output_220v]; battery_voltage = BATTERY_VOLTAGE_MULTIPLIER_A[model_index] * battery_voltage + BATTERY_VOLTAGE_MULTIPLIER_B[model_index]; output_current = OUTPUT_CURRENT_MULTIPLIER_A[model_index][line_unpowered] * output_current + OUTPUT_CURRENT_MULTIPLIER_B[model_index][line_unpowered]; if (ups_model == 190 && line_unpowered) { /* Special calculation for BZ1500 on battery */ output_voltage = battery_voltage * sqrt(output_voltage / 64.0) * OUTPUT_VOLTAGE_MULTIPLIER_A[model_index][line_unpowered][relay_state] - output_current * OUTPUT_VOLTAGE_MULTIPLIER_B[model_index][line_unpowered][relay_state]; output_voltage = 1.5091 * output_voltage + 1.5823; if (output_current > 4.0) output_voltage += output_current * 4.0; if (output_current > 3.0) output_voltage += output_current * 2.0; else if (output_voltage > 0.9) output_voltage += output_current / 3.0; else output_voltage -= 5.0; if (output_voltage < 100.0) output_voltage = 100; } else { output_voltage = OUTPUT_VOLTAGE_MULTIPLIER_A[model_index][line_unpowered][relay_state] * output_voltage + OUTPUT_VOLTAGE_MULTIPLIER_B[model_index][line_unpowered][relay_state]; } if (line_unpowered) { input_frequency = 0; output_frequency = 60; } else { input_frequency = 0.37 * (257 - ((received_packet[21] + received_packet[22] * 256) >> 8)); output_frequency = input_frequency; } apparent_power = output_current * output_voltage; real_power = received_packet[7] + 256 * received_packet[8]; real_power_curve_1 = REAL_POWER_CURVE_SELECTOR_A1[model_index][relay_state] * real_power + REAL_POWER_CURVE_SELECTOR_B1[model_index][relay_state]; real_power_curve_2 = REAL_POWER_CURVE_SELECTOR_A2[model_index][relay_state] * real_power + REAL_POWER_CURVE_SELECTOR_B2[model_index][relay_state]; real_power_curve_3 = REAL_POWER_CURVE_SELECTOR_A3[model_index][relay_state] * real_power + REAL_POWER_CURVE_SELECTOR_B3[model_index][relay_state]; power_difference_1 = fabs(real_power_curve_1 - apparent_power); power_difference_2 = fabs(real_power_curve_2 - apparent_power); power_difference_3 = fabs(real_power_curve_3 - apparent_power); if (power_difference_1 < power_difference_2 && power_difference_1 < power_difference_3) { real_power = REAL_POWER_MULTIPLIER_A1[model_index][relay_state] * real_power + REAL_POWER_MULTIPLIER_B1[model_index][relay_state]; } else if (power_difference_2 < power_difference_3) { real_power = REAL_POWER_MULTIPLIER_A2[model_index][relay_state] * real_power + REAL_POWER_MULTIPLIER_B2[model_index][relay_state]; } else { real_power = REAL_POWER_MULTIPLIER_A3[model_index][relay_state] * real_power + REAL_POWER_MULTIPLIER_B3[model_index][relay_state]; } /* If real power is greater than apparent power, invert values */ if (apparent_power < real_power) { apparent_power = apparent_power + real_power; real_power = apparent_power - real_power; apparent_power = apparent_power - real_power; } input_current = 1.1 * apparent_power / input_voltage; recharging = (0x02 & received_packet[20]) == 0x02; battery_charge = (100.0 * (battery_voltage - MIN_BATTERY_VOLTAGE[model_index])) / (MAX_BATTERY_VOLTAGE[model_index][recharging] - MIN_BATTERY_VOLTAGE[model_index]); }