nut/drivers/microsol-apc.c

189 lines
6.6 KiB
C
Raw Permalink Normal View History

2022-06-29 10:37:36 +00:00
/* microsol-apc.c - APC Back-UPS BR series UPS driver
Copyright (C) 2004 Silvino B. Magalhães <sbm2yk@gmail.com>
2019 Roberto Panerai Velloso <rvelloso@gmail.com>
2021 Ygor A. S. Regados <ygorre@tutanota.com>
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 <ctype.h>
#include <stdio.h>
#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 <sbm2yk@gmail.com>"
"Roberto Panerai Velloso <rvelloso@gmail.com>"
"Ygor A. S. Regados <ygorre@tutanota.com>",
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]);
}