diff --git a/configure.ac b/configure.ac index 640c715840..dbc8955ea7 100644 --- a/configure.ac +++ b/configure.ac @@ -819,7 +819,7 @@ AC_CHECK_FUNCS(strtok_r fileno sigemptyset sigaction, dnl For these we have a fallback implementation via the other, dnl if at least one is available, so initial check is quiet. dnl This typically pops up in POSIX vs. Windows builds: -AC_CHECK_FUNCS(localtime_r localtime_s gmtime_r gmtime_s, +AC_CHECK_FUNCS(localtime_r localtime_s gmtime_r gmtime_s timegm _mkgmtime, [], []) AC_MSG_CHECKING([for at least one gmtime implementation]) @@ -838,6 +838,14 @@ AS_IF([test x"${ac_cv_func_localtime_s}-${ac_cv_func_localtime_r}" = "xno-no"], AC_MSG_RESULT([yes]) ]) +AC_MSG_CHECKING([for at least one timegm implementation]) +AS_IF([test x"${ac_cv_func_timegm}-${ac_cv_func__mkgmtime}" = "xno-no"], [ + AC_MSG_RESULT([no]) + AC_MSG_WARN([Required C library routine timegm nor _mkgmtime was not found]) + ],[ + AC_MSG_RESULT([yes]) + ]) + AC_LANG_PUSH([C]) AC_CHECK_HEADER([string.h], diff --git a/drivers/Makefile.am b/drivers/Makefile.am index 57169b2f91..c7477ae512 100644 --- a/drivers/Makefile.am +++ b/drivers/Makefile.am @@ -360,7 +360,8 @@ nutdrv_qx_SOURCES += $(NUTDRV_QX_SUBDRIVERS) # tracking (which is automatic), but to ensure these files are # distributed by "make dist". -dist_noinst_HEADERS = apc-mib.h apc-iem-mib.h apc-hid.h arduino-hid.h baytech-mib.h bcmxcp.h bcmxcp_ser.h \ +dist_noinst_HEADERS = \ + apc_modbus.h apc-mib.h apc-iem-mib.h apc-hid.h arduino-hid.h baytech-mib.h bcmxcp.h bcmxcp_ser.h \ bcmxcp_io.h belkin.h belkin-hid.h bestpower-mib.h blazer.h cps-hid.h dstate.h \ dummy-ups.h explore-hid.h gamatronic.h genericups.h \ generic_gpio_common.h generic_gpio_libgpiod.h \ diff --git a/drivers/apc_modbus.c b/drivers/apc_modbus.c index 7e437e2fce..8aafce1479 100644 --- a/drivers/apc_modbus.c +++ b/drivers/apc_modbus.c @@ -24,6 +24,7 @@ #endif /* defined NUT_MODBUS_HAS_USB */ #include "timehead.h" #include "nut_stdint.h" +#include "apc_modbus.h" #include #include @@ -31,7 +32,7 @@ #include #define DRIVER_NAME "NUT APC Modbus driver" -#define DRIVER_VERSION "0.01" +#define DRIVER_VERSION "0.10" #if defined NUT_MODBUS_HAS_USB @@ -89,23 +90,30 @@ upsdrv_info_t upsdrv_info = { }; typedef enum { - APC_MODBUS_VALUE_TYPE_INT = 0, - APC_MODBUS_VALUE_TYPE_UINT, - APC_MODBUS_VALUE_TYPE_STRING + APC_VT_INT = 0, + APC_VT_UINT, + APC_VT_STRING } apc_modbus_value_types; -static const apc_modbus_value_types apc_modbus_value_types_max = APC_MODBUS_VALUE_TYPE_STRING; +typedef enum { + APC_VF_NONE = 0, + APC_VF_RW = (1 << 0) +} apc_modbus_value_flags; + +static const apc_modbus_value_types apc_modbus_value_types_max = APC_VT_STRING; + +typedef union { + int64_t int_value; + uint64_t uint_value; + char *string_value; +} apc_modbus_value_data_t; typedef struct { apc_modbus_value_types type; const char *format; int scale; void *variable_ptr; - union { - int64_t int_value; - uint64_t uint_value; - char *string_value; - } data; + apc_modbus_value_data_t data; } apc_modbus_value_t; typedef struct { @@ -178,6 +186,37 @@ static int _apc_modbus_to_string(const uint16_t *value, const size_t value_len, return 1; } +static int _apc_modbus_from_string(const char *value, uint16_t *output, size_t output_len) +{ + size_t value_len, vi, oi; + uint16_t tmp; + + if (value == NULL || output == NULL) { + /* Invalid parameters */ + return 0; + } + + value_len = strlen(value); + + if (value_len > (output_len * sizeof(uint16_t))) { + /* Output buffer too small */ + return 0; + } + + for (vi = 0, oi = 0; vi < value_len && oi < output_len; vi += 2, oi++) { + tmp = value[vi] << 8; + if (vi + 1 < value_len) + tmp |= value[vi + 1]; + output[oi] = tmp; + } + + for (; oi < output_len; oi++) { + output[oi] = 0; + } + + return 1; +} + /* Converts a Modbus integer to a uint64_t. * See MPAO-98KJ7F_EN section 1.3.1 Numbers. */ static int _apc_modbus_to_uint64(const uint16_t *value, const size_t value_len, uint64_t *output) @@ -197,6 +236,51 @@ static int _apc_modbus_to_uint64(const uint16_t *value, const size_t value_len, return 1; } +static int _apc_modbus_from_uint64(uint64_t value, uint16_t *output, size_t output_len) +{ + ssize_t oi; + size_t bits; + + if (output == NULL) { + /* Invalid parameters */ + return 0; + } + + bits = output_len * sizeof(uint16_t) * 8; + if (bits < 64) { + if (value > ((1ULL << bits) - 1)) { + /* Overflow */ + return 0; + } + } + + for (oi = output_len - 1; oi >= 0; oi--) { + output[oi] = (uint16_t)(value & 0xFFFF); + value >>= 16; + } + + return 1; +} + +static int _apc_modbus_from_uint64_string(const char *value, uint16_t *output, size_t output_len) +{ + uint64_t value_uint; + char *endptr; + + if (value == NULL || output == NULL) { + /* Invalid parameters */ + return 0; + } + + errno = 0; + value_uint = strtoull(value, &endptr, 0); + if (endptr == value || *endptr != '\0' || errno > 0) { + return 0; + } + + return _apc_modbus_from_uint64(value_uint, output, output_len); +} + static int _apc_modbus_to_int64(const uint16_t *value, const size_t value_len, int64_t *output) { size_t shiftval; @@ -212,6 +296,50 @@ static int _apc_modbus_to_int64(const uint16_t *value, const size_t value_len, i return 1; } +static int _apc_modbus_from_int64(int64_t value, uint16_t *output, size_t output_len) +{ + ssize_t oi; + size_t bits; + + if (output == NULL) { + /* Invalid parameters */ + return 0; + } + + bits = output_len * sizeof(uint16_t) * 8; + if (value > ((1LL << (bits - 1)) - 1) || + value < -(1LL << (bits - 1))) { + /* Overflow */ + return 0; + } + + for (oi = output_len - 1; oi >= 0; oi--) { + output[oi] = (uint16_t)(value & 0xFFFF); + value >>= 16; + } + + return 1; +} + +static int _apc_modbus_from_int64_string(const char *value, uint16_t *output, size_t output_len) +{ + int64_t value_int; + char *endptr; + + if (value == NULL || output == NULL) { + /* Invalid parameters */ + return 0; + } + + errno = 0; + value_int = strtoll(value, &endptr, 0); + if (endptr == value || *endptr != '\0' || errno > 0) { + return 0; + } + + return _apc_modbus_from_int64(value_int, output, output_len); +} + static int _apc_modbus_to_double(const apc_modbus_value_t *value, double *output) { int factor; @@ -225,13 +353,13 @@ static int _apc_modbus_to_double(const apc_modbus_value_t *value, double *output assert(value->type <= apc_modbus_value_types_max); switch (value->type) { - case APC_MODBUS_VALUE_TYPE_INT: + case APC_VT_INT: *output = (double)value->data.int_value / factor; break; - case APC_MODBUS_VALUE_TYPE_UINT: + case APC_VT_UINT: *output = (double)value->data.uint_value / factor; break; - case APC_MODBUS_VALUE_TYPE_STRING: + case APC_VT_STRING: return 0; } @@ -332,7 +460,7 @@ static int _apc_modbus_voltage_to_nut(const apc_modbus_value_t *value, char *out return 0; } - if (value->type != APC_MODBUS_VALUE_TYPE_UINT) { + if (value->type != APC_VT_UINT) { return 0; } @@ -360,7 +488,7 @@ static int _apc_modbus_efficiency_to_nut(const apc_modbus_value_t *value, char * return 0; } - if (value->type != APC_MODBUS_VALUE_TYPE_INT) { + if (value->type != APC_VT_INT) { return 0; } @@ -413,7 +541,7 @@ static int _apc_modbus_status_change_cause_to_nut(const apc_modbus_value_t *valu return 0; } - if (value->type != APC_MODBUS_VALUE_TYPE_UINT) { + if (value->type != APC_VT_UINT) { return 0; } @@ -526,35 +654,150 @@ static int _apc_modbus_status_change_cause_to_nut(const apc_modbus_value_t *valu static apc_modbus_converter_t _apc_modbus_status_change_cause_conversion = { _apc_modbus_status_change_cause_to_nut, NULL }; +static int _apc_modbus_string_join(const char *values[], size_t values_len, const char *separator, char *output, size_t output_len) +{ + size_t i; + size_t output_idx; + int res; + + if (values == NULL || values_len == 0 || separator == NULL || output == NULL || output_len == 0) { + /* Invalid parameters */ + return 0; + } + + output_idx = 0; + + for (i = 0; i < values_len && output_idx < output_len; i++) { + if (values[i] == NULL) + continue; + + if (i == 0) { + res = snprintf(output + output_idx, output_len - output_idx, "%s", values[i]); + } else { + res = snprintf(output + output_idx, output_len - output_idx, "%s%s", separator, values[i]); + } + + if (res < 0 || (size_t)res >= output_len) { + return 0; + } + + output_idx += res; + } + + return 1; +} + +static int _apc_modbus_battery_test_status_to_nut(const apc_modbus_value_t *value, char *output, size_t output_len) +{ + const char *result, *source, *modifier; + const char *values[3]; + + if (value == NULL || output == NULL || output_len == 0) { + /* Invalid parameters */ + return 0; + } + + if (value->type != APC_VT_UINT) { + return 0; + } + + result = NULL; + if ((value->data.uint_value & APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_PENDING)) { + result = "Pending"; + } else if ((value->data.uint_value & APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_INPROGRESS)) { + result = "InProgress"; + } else if ((value->data.uint_value & APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_PASSED)) { + result = "Passed"; + } else if ((value->data.uint_value & APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_FAILED)) { + result = "Failed"; + } else if ((value->data.uint_value & APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_REFUSED)) { + result = "Refused"; + } else if ((value->data.uint_value & APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_ABORTED)) { + result = "Aborted"; + } + + source = NULL; + if ((value->data.uint_value & APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_SOURCE_PROTOCOL)) { + source = "Source: Protocol"; + } else if ((value->data.uint_value & APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_SOURCE_LOCALUI)) { + source = "Source: LocalUI"; + } else if ((value->data.uint_value & APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_SOURCE_INTERNAL)) { + source = "Source: Internal"; + } + + modifier = NULL; + if ((value->data.uint_value & APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_MOD_INVALIDSTATE)) { + modifier = "Modifier: InvalidState"; + } else if ((value->data.uint_value & APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_MOD_INTERNALFAULT)) { + modifier = "Modifier: InternalFault"; + } else if ((value->data.uint_value & APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_MOD_STATEOFCHARGENOTACCEPTABLE)) { + modifier = "Modifier: StateOfChargeNotAcceptable"; + } + + values[0] = result; + values[1] = source; + values[2] = modifier; + return _apc_modbus_string_join(values, SIZEOF_ARRAY(values), ", ", output, output_len); +} + +static apc_modbus_converter_t _apc_modbus_battery_test_status_conversion = { _apc_modbus_battery_test_status_to_nut, NULL }; + +static const time_t apc_date_start_offset = 946684800; /* 2000-01-01 00:00 */ + static int _apc_modbus_date_to_nut(const apc_modbus_value_t *value, char *output, size_t output_len) { - struct tm *tm_info; + struct tm tm_info; time_t time_stamp; - const time_t start_offset = 946684800; /* 2000-01-01 00:00 */ if (value == NULL || output == NULL || output_len == 0) { /* Invalid parameters */ return 0; } - if (value->type != APC_MODBUS_VALUE_TYPE_UINT) { + if (value->type != APC_VT_UINT) { return 0; } - time_stamp = ((int64_t)value->data.uint_value * 86400) + start_offset; - tm_info = gmtime(&time_stamp); - strftime(output, output_len, "%Y-%m-%d", tm_info); + time_stamp = ((int64_t)value->data.uint_value * 86400) + apc_date_start_offset; + gmtime_r(&time_stamp, &tm_info); + strftime(output, output_len, "%Y-%m-%d", &tm_info); return 1; } -static apc_modbus_converter_t _apc_modbus_date_conversion = { _apc_modbus_date_to_nut, NULL }; +static int _apc_modbus_date_from_nut(const char *value, uint16_t *output, size_t output_len) +{ + struct tm tm_struct; + time_t epoch_time; + uint64_t uint_value; + + if (value == NULL || output == NULL || output_len == 0) { + /* Invalid parameters */ + return 0; + } + + memset(&tm_struct, 0, sizeof(tm_struct)); + if (strptime(value, "%Y-%m-%d", &tm_struct) == NULL) { + return 0; + } + + if ((epoch_time = timegm(&tm_struct)) == -1) { + return 0; + } + + uint_value = (epoch_time - apc_date_start_offset) / 86400; + + return _apc_modbus_from_uint64(uint_value, output, output_len); +} + +static apc_modbus_converter_t _apc_modbus_date_conversion = { _apc_modbus_date_to_nut, _apc_modbus_date_from_nut }; typedef struct { const char *nut_variable_name; size_t modbus_addr; size_t modbus_len; /* Number of uint16_t registers */ apc_modbus_value_types value_type; + apc_modbus_value_flags value_flags; apc_modbus_converter_t *value_converter; const char *value_format; int value_scale; @@ -563,50 +806,74 @@ typedef struct { /* Values that only need to be updated once on startup */ static apc_modbus_register_t apc_modbus_register_map_inventory[] = { - { "ups.firmware", 516, 8, APC_MODBUS_VALUE_TYPE_STRING, NULL, "%s", 0, NULL }, - { "ups.model", 532, 16, APC_MODBUS_VALUE_TYPE_STRING, NULL, "%s", 0, NULL }, /* also device.model, filled automatically */ - { "ups.serial", 564, 8, APC_MODBUS_VALUE_TYPE_STRING, NULL, "%s", 0, NULL }, /* also device.serial, filled automatically */ - { "ups.power.nominal", 588, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_double_conversion, "%.0f", 0, &power_nominal }, - { "ups.realpower.nominal", 589, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_double_conversion, "%.0f", 0, &realpower_nominal }, - { "ups.mfr.date", 591, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_date_conversion, NULL, 0, NULL }, - { "battery.date", 595, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_date_conversion, NULL, 0, NULL }, - { "ups.id", 596, 8, APC_MODBUS_VALUE_TYPE_STRING, NULL, "%s", 0, NULL }, - { NULL, 0, 0, 0, NULL, NULL, 0.0f, NULL } + { "ups.firmware", 516, 8, APC_VT_STRING, 0, NULL, "%s", 0, NULL }, + { "ups.model", 532, 16, APC_VT_STRING, 0, NULL, "%s", 0, NULL }, /* also device.model, filled automatically */ + { "ups.serial", 564, 8, APC_VT_STRING, 0, NULL, "%s", 0, NULL }, /* also device.serial, filled automatically */ + { "ups.power.nominal", 588, 1, APC_VT_UINT, 0, &_apc_modbus_double_conversion, "%.0f", 0, &power_nominal }, + { "ups.realpower.nominal", 589, 1, APC_VT_UINT, 0, &_apc_modbus_double_conversion, "%.0f", 0, &realpower_nominal }, + { "ups.mfr.date", 591, 1, APC_VT_UINT, 0, &_apc_modbus_date_conversion, NULL, 0, NULL }, + { "battery.date", 595, 1, APC_VT_UINT, APC_VF_RW, &_apc_modbus_date_conversion, NULL, 0, NULL }, + { "ups.id", 596, 8, APC_VT_STRING, APC_VF_RW, NULL, "%s", 0, NULL }, + { "outlet.group.0.name", 604, 8, APC_VT_STRING, APC_VF_RW, NULL, "%s", 0, NULL }, + { "outlet.group.1.name", 612, 8, APC_VT_STRING, APC_VF_RW, NULL, "%s", 0, NULL }, + { "outlet.group.2.name", 620, 8, APC_VT_STRING, APC_VF_RW, NULL, "%s", 0, NULL }, + { "outlet.group.3.name", 628, 8, APC_VT_STRING, APC_VF_RW, NULL, "%s", 0, NULL }, + { NULL, 0, 0, 0, 0, NULL, NULL, 0.0f, NULL } }; static apc_modbus_register_t apc_modbus_register_map_status[] = { - { "input.transfer.reason", 2, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_status_change_cause_conversion, NULL, 0, NULL }, - { NULL, 0, 0, 0, NULL, NULL, 0.0f, NULL } + { "input.transfer.reason", 2, 1, APC_VT_UINT, 0, &_apc_modbus_status_change_cause_conversion, NULL, 0, NULL }, + { "ups.test.result", 23, 1, APC_VT_UINT, 0, &_apc_modbus_battery_test_status_conversion, NULL, 0, NULL }, + { NULL, 0, 0, 0, 0, NULL, NULL, 0.0f, NULL } }; static apc_modbus_register_t apc_modbus_register_map_dynamic[] = { - { "battery.runtime", 128, 2, APC_MODBUS_VALUE_TYPE_UINT, NULL, "%u", 0, NULL }, - { "battery.charge", 130, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_double_conversion, "%.2f", 9, NULL }, - { "battery.voltage", 131, 1, APC_MODBUS_VALUE_TYPE_INT, &_apc_modbus_double_conversion, "%.2f", 5, NULL }, - { "battery.date.maintenance", 133, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_date_conversion, NULL, 0, NULL }, - { "battery.temperature", 135, 1, APC_MODBUS_VALUE_TYPE_INT, &_apc_modbus_double_conversion, "%.2f", 7, NULL }, - { "ups.load", 136, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_double_conversion, "%.2f", 8, NULL }, - { "ups.realpower", 136, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_power_conversion, "%.2f", 8, &realpower_nominal }, - { "ups.power", 138, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_power_conversion, "%.2f", 8, &power_nominal }, - { "output.current", 140, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_double_conversion, "%.2f", 5, NULL }, - { "output.voltage", 142, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_double_conversion, "%.2f", 6, NULL }, - { "output.frequency", 144, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_double_conversion, "%.2f", 7, NULL }, - { "experimental.output.energy", 145, 2, APC_MODBUS_VALUE_TYPE_UINT, NULL, "%u", 0, NULL }, - { "input.voltage", 151, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_voltage_conversion, "%.2f", 6, NULL }, - { "ups.efficiency", 154, 1, APC_MODBUS_VALUE_TYPE_INT, &_apc_modbus_efficiency_conversion, "%.1f", 7, NULL }, - { "ups.timer.shutdown", 155, 1, APC_MODBUS_VALUE_TYPE_INT, NULL, "%d", 0, NULL }, - { "ups.timer.start", 156, 1, APC_MODBUS_VALUE_TYPE_INT, NULL, "%d", 0, NULL }, - { "ups.timer.reboot", 157, 2, APC_MODBUS_VALUE_TYPE_INT, NULL, "%d", 0, NULL }, - { NULL, 0, 0, 0, NULL, NULL, 0.0f, NULL } + { "battery.runtime", 128, 2, APC_VT_UINT, 0, NULL, "%u", 0, NULL }, + { "battery.charge", 130, 1, APC_VT_UINT, 0, &_apc_modbus_double_conversion, "%.2f", 9, NULL }, + { "battery.voltage", 131, 1, APC_VT_INT, 0, &_apc_modbus_double_conversion, "%.2f", 5, NULL }, + { "battery.date.maintenance", 133, 1, APC_VT_UINT, 0, &_apc_modbus_date_conversion, NULL, 0, NULL }, + { "battery.temperature", 135, 1, APC_VT_INT, 0, &_apc_modbus_double_conversion, "%.2f", 7, NULL }, + { "ups.load", 136, 1, APC_VT_UINT, 0, &_apc_modbus_double_conversion, "%.2f", 8, NULL }, + { "ups.realpower", 136, 1, APC_VT_UINT, 0, &_apc_modbus_power_conversion, "%.2f", 8, &realpower_nominal }, + { "ups.power", 138, 1, APC_VT_UINT, 0, &_apc_modbus_power_conversion, "%.2f", 8, &power_nominal }, + { "output.current", 140, 1, APC_VT_UINT, 0, &_apc_modbus_double_conversion, "%.2f", 5, NULL }, + { "output.voltage", 142, 1, APC_VT_UINT, 0, &_apc_modbus_double_conversion, "%.2f", 6, NULL }, + { "output.frequency", 144, 1, APC_VT_UINT, 0, &_apc_modbus_double_conversion, "%.2f", 7, NULL }, + { "experimental.output.energy", 145, 2, APC_VT_UINT, 0, NULL, "%u", 0, NULL }, + { "input.voltage", 151, 1, APC_VT_UINT, 0, &_apc_modbus_voltage_conversion, "%.2f", 6, NULL }, + { "ups.efficiency", 154, 1, APC_VT_INT, 0, &_apc_modbus_efficiency_conversion, "%.1f", 7, NULL }, + { "ups.timer.shutdown", 155, 1, APC_VT_INT, 0, NULL, "%d", 0, NULL }, + { "ups.timer.start", 156, 1, APC_VT_INT, 0, NULL, "%d", 0, NULL }, + { "ups.timer.reboot", 157, 2, APC_VT_INT, 0, NULL, "%d", 0, NULL }, + { NULL, 0, 0, 0, 0, NULL, NULL, 0.0f, NULL } }; static apc_modbus_register_t apc_modbus_register_map_static[] = { - { "input.transfer.high", 1026, 1, APC_MODBUS_VALUE_TYPE_UINT, NULL, "%u", 0, NULL }, - { "input.transfer.low", 1027, 1, APC_MODBUS_VALUE_TYPE_UINT, NULL, "%u", 0, NULL }, - { "ups.delay.shutdown", 1029, 1, APC_MODBUS_VALUE_TYPE_INT, NULL, "%d", 0, NULL }, - { "ups.delay.start", 1030, 1, APC_MODBUS_VALUE_TYPE_INT, NULL, "%d", 0, NULL }, - { "ups.delay.reboot", 1031, 2, APC_MODBUS_VALUE_TYPE_INT, NULL, "%d", 0, NULL }, - { NULL, 0, 0, 0, NULL, NULL, 0.0f, NULL } + { "input.transfer.high", 1026, 1, APC_VT_UINT, APC_VF_RW, NULL, "%u", 0, NULL }, + { "input.transfer.low", 1027, 1, APC_VT_UINT, APC_VF_RW, NULL, "%u", 0, NULL }, + { "ups.delay.shutdown", 1029, 1, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "ups.delay.start", 1030, 1, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "ups.delay.reboot", 1031, 2, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "outlet.group.0.delay.shutdown", 1029, 1, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "outlet.group.0.delay.start", 1030, 1, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "outlet.group.0.delay.reboot", 1031, 2, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "outlet.group.1.delay.shutdown", 1034, 1, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "outlet.group.1.delay.start", 1035, 1, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "outlet.group.1.delay.reboot", 1036, 2, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "outlet.group.2.delay.shutdown", 1039, 1, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "outlet.group.2.delay.start", 1040, 1, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "outlet.group.2.delay.reboot", 1041, 2, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "outlet.group.3.delay.shutdown", 1044, 1, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "outlet.group.3.delay.start", 1045, 1, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "outlet.group.3.delay.reboot", 1046, 2, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { NULL, 0, 0, 0, 0, NULL, NULL, 0.0f, NULL } +}; + +static apc_modbus_register_t* apc_modbus_register_maps[] = { + apc_modbus_register_map_inventory, + apc_modbus_register_map_status, + apc_modbus_register_map_dynamic, + apc_modbus_register_map_static }; static void _apc_modbus_close(int free_modbus) @@ -757,6 +1024,7 @@ static int _apc_modbus_update_value(apc_modbus_register_t *regs_info, const uint { apc_modbus_value_t value; char strbuf[33], nutvbuf[128]; + int dstate_flags; if (regs_info == NULL || regs == NULL || regs_len == 0) { /* Invalid parameters */ @@ -770,19 +1038,19 @@ static int _apc_modbus_update_value(apc_modbus_register_t *regs_info, const uint assert(regs_info->value_type <= apc_modbus_value_types_max); switch (regs_info->value_type) { - case APC_MODBUS_VALUE_TYPE_STRING: + case APC_VT_STRING: _apc_modbus_to_string(regs, regs_info->modbus_len, strbuf, sizeof(strbuf)); value.data.string_value = strbuf; break; - case APC_MODBUS_VALUE_TYPE_INT: + case APC_VT_INT: _apc_modbus_to_int64(regs, regs_info->modbus_len, &value.data.int_value); break; - case APC_MODBUS_VALUE_TYPE_UINT: + case APC_VT_UINT: _apc_modbus_to_uint64(regs, regs_info->modbus_len, &value.data.uint_value); break; } - if (regs_info->value_converter != NULL) { + if (regs_info->value_converter != NULL && regs_info->value_converter->apc_to_nut != NULL) { /* If we have a converter, use it and set the value as a string */ if (!regs_info->value_converter->apc_to_nut(&value, nutvbuf, sizeof(nutvbuf))) { upslogx(LOG_ERR, "%s: Failed to convert register %" PRIuSIZE ":%" PRIuSIZE, __func__, @@ -799,13 +1067,13 @@ static int _apc_modbus_update_value(apc_modbus_register_t *regs_info, const uint #endif assert(regs_info->value_type <= apc_modbus_value_types_max); switch (regs_info->value_type) { - case APC_MODBUS_VALUE_TYPE_STRING: + case APC_VT_STRING: dstate_setinfo(regs_info->nut_variable_name, regs_info->value_format, value.data.string_value); break; - case APC_MODBUS_VALUE_TYPE_INT: + case APC_VT_INT: dstate_setinfo(regs_info->nut_variable_name, regs_info->value_format, value.data.int_value); break; - case APC_MODBUS_VALUE_TYPE_UINT: + case APC_VT_UINT: dstate_setinfo(regs_info->nut_variable_name, regs_info->value_format, value.data.uint_value); break; } @@ -814,6 +1082,18 @@ static int _apc_modbus_update_value(apc_modbus_register_t *regs_info, const uint #endif } + dstate_flags = 0; + if (regs_info->value_type == APC_VT_STRING) { + dstate_flags |= ST_FLAG_STRING; + } + if ((regs_info->value_flags & APC_VF_RW)) { + dstate_flags |= ST_FLAG_RW; + } + dstate_setflags(regs_info->nut_variable_name, dstate_flags); + if (regs_info->value_type == APC_VT_STRING) { + dstate_setaux(regs_info->nut_variable_name, regs_info->modbus_len * sizeof(uint16_t)); + } + return 1; } @@ -837,11 +1117,38 @@ static int _apc_modbus_process_registers(apc_modbus_register_t* values, const ui static int _apc_modbus_read_inventory(void) { - uint16_t regbuf[88]; + uint16_t regbuf[120]; + int start_addr; + uint16_t sog_relay_config; + int outlet_group_count; /* Inventory Information */ - if (_apc_modbus_read_registers(modbus_ctx, 516, 88, regbuf)) { - _apc_modbus_process_registers(apc_modbus_register_map_inventory, regbuf, 88, 516); + start_addr = apc_modbus_register_map_inventory[0].modbus_addr; + if (_apc_modbus_read_registers(modbus_ctx, start_addr, SIZEOF_ARRAY(regbuf), regbuf)) { + sog_relay_config = regbuf[APC_MODBUS_SOGRELAYCONFIGSETTING_BF_REG - start_addr]; + + outlet_group_count = 0; + if ((sog_relay_config & APC_MODBUS_SOGRELAYCONFIGSETTING_BF_MOG_PRESENT)) { + outlet_group_count++; + } + if ((sog_relay_config & APC_MODBUS_SOGRELAYCONFIGSETTING_BF_SOG_0_PRESENT)) { + outlet_group_count++; + } + if ((sog_relay_config & APC_MODBUS_SOGRELAYCONFIGSETTING_BF_SOG_1_PRESENT)) { + outlet_group_count++; + } + if ((sog_relay_config & APC_MODBUS_SOGRELAYCONFIGSETTING_BF_SOG_2_PRESENT)) { + outlet_group_count++; + } + /* Documentation says there is a bit for SOG3, but everything else does not have it */ + if ((sog_relay_config & APC_MODBUS_SOGRELAYCONFIGSETTING_BF_SOG_3_PRESENT)) { + upslogx(LOG_WARNING, "%s: SOG3 present, but we don't know how to use it", __func__); + outlet_group_count++; + } + + dstate_setinfo("outlet.group.count", "%d", outlet_group_count); + + _apc_modbus_process_registers(apc_modbus_register_map_inventory, regbuf, SIZEOF_ARRAY(regbuf), start_addr); } else { return 0; } @@ -849,8 +1156,193 @@ static int _apc_modbus_read_inventory(void) return 1; } +static int _apc_modbus_setvar(const char *nut_varname, const char *str_value) +{ + size_t mi, i; + int addr, nb, r; + apc_modbus_register_t *apc_map = NULL, *apc_value = NULL; + uint16_t reg_value[16]; + + for (mi = 0; mi < SIZEOF_ARRAY(apc_modbus_register_maps) && apc_value == NULL; mi++) { + apc_map = apc_modbus_register_maps[mi]; + + for (i = 0; apc_map[i].nut_variable_name; i++) { + if (!strcasecmp(nut_varname, apc_map[i].nut_variable_name)) { + apc_value = &apc_map[i]; + break; + } + } + } + + if (!apc_map || !apc_value) { + upslogx(LOG_WARNING, "%s: [%s] is unknown", __func__, nut_varname); + return STAT_SET_UNKNOWN; + } + + if (!(apc_value->value_flags & APC_VF_RW)) { + upslogx(LOG_WARNING, "%s: [%s] is not writable", __func__, nut_varname); + return STAT_SET_INVALID; + } + + assert(apc_value->modbus_len < SIZEOF_ARRAY(reg_value)); + + if (apc_value->value_converter && apc_value->value_converter->nut_to_apc) { + if (!apc_value->value_converter->nut_to_apc(str_value, reg_value, apc_value->modbus_len)) { + upslogx(LOG_WARNING, "%s: [%s] failed to convert value", __func__, nut_varname); + return STAT_SET_CONVERSION_FAILED; + } + } else { + assert(apc_value->value_type <= apc_modbus_value_types_max); + switch (apc_value->value_type) { + case APC_VT_STRING: + r = _apc_modbus_from_string(str_value, reg_value, apc_value->modbus_len); + break; + case APC_VT_INT: + r = _apc_modbus_from_int64_string(str_value, reg_value, apc_value->modbus_len); + break; + case APC_VT_UINT: + r = _apc_modbus_from_uint64_string(str_value, reg_value, apc_value->modbus_len); + break; + } + + if (!r) { + upslogx(LOG_WARNING, "%s: [%s] failed to convert value", __func__, nut_varname); + return STAT_SET_CONVERSION_FAILED; + } + } + + addr = apc_value->modbus_addr; + nb = apc_value->modbus_len; + if (modbus_write_registers(modbus_ctx, addr, nb, reg_value) < 0) { + upslogx(LOG_ERR, "%s: Write of %d:%d failed: %s (%s)", __func__, addr, addr + nb, modbus_strerror(errno), device_path); + _apc_modbus_handle_error(modbus_ctx); + return STAT_SET_FAILED; + } + + /* There seem to be some communication problems if we don't wait after writing. + * Maybe there is some register we need to poll for write completion? + */ + usleep(100000); + + upslogx(LOG_INFO, "SET %s='%s'", nut_varname, str_value); + + if (_apc_modbus_read_registers(modbus_ctx, addr, nb, reg_value)) { + _apc_modbus_process_registers(apc_map, reg_value, nb, addr); + } + + return STAT_SET_HANDLED; +} + +typedef struct { + const char *nut_command_name; + size_t modbus_addr; + size_t modbus_len; /* Number of uint16_t registers */ + uint64_t value; +} apc_modbus_command_t; + +static apc_modbus_command_t apc_modbus_command_map[] = { + { "test.battery.start", APC_MODBUS_REPLACEBATTERYTESTCOMMAND_BF_REG, 1, APC_MODBUS_REPLACEBATTERYTESTCOMMAND_BF_START }, + { "test.battery.stop", APC_MODBUS_REPLACEBATTERYTESTCOMMAND_BF_REG, 1, APC_MODBUS_REPLACEBATTERYTESTCOMMAND_BF_ABORT }, + { "test.panel.start", APC_MODBUS_USERINTERFACECOMMAND_BF_REG, 1, APC_MODBUS_USERINTERFACECOMMAND_BF_SHORT_TEST }, + { "calibrate.start", APC_MODBUS_RUNTIMECALIBRATIONCOMMAND_BF_REG, 1, APC_MODBUS_RUNTIMECALIBRATIONCOMMAND_BF_START }, + { "calibrate.stop", APC_MODBUS_RUNTIMECALIBRATIONCOMMAND_BF_REG, 1, APC_MODBUS_RUNTIMECALIBRATIONCOMMAND_BF_ABORT }, + { "bypass.start", APC_MODBUS_UPSCOMMAND_BF_REG, 2, APC_MODBUS_UPSCOMMAND_BF_OUTPUT_INTO_BYPASS }, + { "bypass.stop", APC_MODBUS_UPSCOMMAND_BF_REG, 2, APC_MODBUS_UPSCOMMAND_BF_OUTPUT_OUT_OF_BYPASS }, + { "beeper.mute", APC_MODBUS_USERINTERFACECOMMAND_BF_REG, 1, APC_MODBUS_USERINTERFACECOMMAND_BF_MUTE_ALL_ACTIVE_AUDIBLE_ALARMS }, + { "load.off", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_OFF | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP }, + { "load.on", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_ON | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP }, + { "load.off.delay", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_OFF | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP | APC_MODBUS_OUTLETCOMMAND_BF_MOD_USE_OFF_DELAY }, + { "load.on.delay", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_ON | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP | APC_MODBUS_OUTLETCOMMAND_BF_MOD_USE_ON_DELAY }, + { "shutdown.return", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_SHUTDOWN | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP | APC_MODBUS_OUTLETCOMMAND_BF_MOD_USE_OFF_DELAY }, + { "shutdown.stayoff", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_OFF | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP | APC_MODBUS_OUTLETCOMMAND_BF_MOD_USE_OFF_DELAY }, + { "shutdown.reboot", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_REBOOT | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP }, + { "shutdown.reboot.graceful", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_REBOOT | APC_MODBUS_OUTLETCOMMAND_BF_MOD_USE_OFF_DELAY }, + { "outlet.0.shutdown.return", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_SHUTDOWN | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP | APC_MODBUS_OUTLETCOMMAND_BF_MOD_USE_OFF_DELAY }, + { "outlet.0.load.off", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_OFF | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP }, + { "outlet.0.load.on", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_ON | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP }, + { "outlet.0.load.cycle", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_REBOOT | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP }, + { "outlet.1.shutdown.return", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_SHUTDOWN | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_0 | APC_MODBUS_OUTLETCOMMAND_BF_MOD_USE_OFF_DELAY }, + { "outlet.1.load.off", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_OFF | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_0 }, + { "outlet.1.load.on", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_ON | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_0 }, + { "outlet.1.load.cycle", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_REBOOT | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_0 }, + { "outlet.2.shutdown.return", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_SHUTDOWN | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_1 | APC_MODBUS_OUTLETCOMMAND_BF_MOD_USE_OFF_DELAY }, + { "outlet.2.load.off", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_OFF | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_1 }, + { "outlet.2.load.on", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_ON | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_1 }, + { "outlet.2.load.cycle", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_REBOOT | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_1 }, + { "outlet.3.shutdown.return", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_SHUTDOWN | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_2 | APC_MODBUS_OUTLETCOMMAND_BF_MOD_USE_OFF_DELAY }, + { "outlet.3.load.off", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_OFF | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_2 }, + { "outlet.3.load.on", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_ON | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_2 }, + { "outlet.3.load.cycle", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_REBOOT | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_2 }, + { NULL, 0, 0, 0 } +}; + +static int _apc_modbus_instcmd(const char *nut_cmdname, const char *extra) +{ + size_t i; + int addr, nb; + apc_modbus_command_t *apc_command = NULL; + uint16_t value[4]; /* Max 64-bit */ + + NUT_UNUSED_VARIABLE(extra); + + for (i = 0; apc_modbus_command_map[i].nut_command_name; i++) { + if (!strcasecmp(nut_cmdname, apc_modbus_command_map[i].nut_command_name)) { + apc_command = &apc_modbus_command_map[i]; + break; + } + } + + if (!apc_command) { + upslogx(LOG_WARNING, "%s: [%s] is unknown", __func__, nut_cmdname); + return STAT_INSTCMD_UNKNOWN; + } + + assert(apc_command->modbus_len <= SIZEOF_ARRAY(value)); + + if (!_apc_modbus_from_uint64(apc_command->value, value, apc_command->modbus_len)) { + upslogx(LOG_WARNING, "%s: [%s] failed to convert value", __func__, nut_cmdname); + return STAT_INSTCMD_CONVERSION_FAILED; + } + + addr = apc_command->modbus_addr; + nb = apc_command->modbus_len; + if (modbus_write_registers(modbus_ctx, addr, nb, value) < 0) { + upslogx(LOG_ERR, "%s: Write of %d:%d failed: %s (%s)", __func__, addr, addr + nb, modbus_strerror(errno), device_path); + _apc_modbus_handle_error(modbus_ctx); + return STAT_INSTCMD_FAILED; + } + + return STAT_INSTCMD_HANDLED; +} + void upsdrv_initinfo(void) { + size_t i; + if (!_apc_modbus_read_inventory()) { fatalx(EXIT_FAILURE, "Can't read inventory information from the UPS"); } @@ -858,6 +1350,14 @@ void upsdrv_initinfo(void) dstate_setinfo("ups.mfr", "American Power Conversion"); /* also device.mfr, filled automatically */ dstate_setinfo("device.type", "ups"); + + for (i = 0; apc_modbus_command_map[i].nut_command_name; i++) { + dstate_addcmd(apc_modbus_command_map[i].nut_command_name); + } + + upsh.setvar = _apc_modbus_setvar; + upsh.instcmd = _apc_modbus_instcmd; + } void upsdrv_updateinfo(void) @@ -950,8 +1450,8 @@ void upsdrv_updateinfo(void) } /* Static Data */ - if (_apc_modbus_read_registers(modbus_ctx, 1026, 7, regbuf)) { - _apc_modbus_process_registers(apc_modbus_register_map_static, regbuf, 7, 1026); + if (_apc_modbus_read_registers(modbus_ctx, 1026, 22, regbuf)) { + _apc_modbus_process_registers(apc_modbus_register_map_static, regbuf, 22, 1026); } else { dstate_datastale(); return; @@ -964,9 +1464,7 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { - /* TODO: replace with a proper shutdown function */ - upslogx(LOG_ERR, "shutdown not supported"); - set_exit_flag(-1); + modbus_write_register(modbus_ctx, APC_MODBUS_OUTLETCOMMAND_BF_REG, APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_SHUTDOWN | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP); } void upsdrv_help(void) @@ -1341,6 +1839,9 @@ void upsdrv_initups(void) } #if defined NUT_MODBUS_HAS_USB + /* This creates an exact matcher after the first connection so that on + * reconnect we are more likely to match the exact device we connected to + * the first time. */ _apc_modbus_create_reopen_matcher(); #endif /* defined NUT_MODBUS_HAS_USB */ diff --git a/drivers/apc_modbus.h b/drivers/apc_modbus.h new file mode 100644 index 0000000000..d175eb4974 --- /dev/null +++ b/drivers/apc_modbus.h @@ -0,0 +1,91 @@ +/* apc_modbus.h - Driver for APC Modbus UPS + * Copyright © 2023 Axel Gembe + * + * 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 + */ + +#ifndef APC_MODBUS_H +#define APC_MODBUS_H + +#define APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_PENDING (1 << 0) +#define APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_INPROGRESS (1 << 1) +#define APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_PASSED (1 << 2) +#define APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_FAILED (1 << 3) +#define APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_REFUSED (1 << 4) +#define APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_ABORTED (1 << 5) +#define APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_SOURCE_PROTOCOL (1 << 6) +#define APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_SOURCE_LOCALUI (1 << 7) +#define APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_SOURCE_INTERNAL (1 << 8) +#define APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_MOD_INVALIDSTATE (1 << 9) +#define APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_MOD_INTERNALFAULT (1 << 10) +#define APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_MOD_STATEOFCHARGENOTACCEPTABLE (1 << 11) + +#define APC_MODBUS_SOGRELAYCONFIGSETTING_BF_REG 590 +#define APC_MODBUS_SOGRELAYCONFIGSETTING_BF_MOG_PRESENT (1 << 0) +#define APC_MODBUS_SOGRELAYCONFIGSETTING_BF_SOG_0_PRESENT (1 << 1) +#define APC_MODBUS_SOGRELAYCONFIGSETTING_BF_SOG_1_PRESENT (1 << 2) +#define APC_MODBUS_SOGRELAYCONFIGSETTING_BF_SOG_2_PRESENT (1 << 3) +#define APC_MODBUS_SOGRELAYCONFIGSETTING_BF_SOG_3_PRESENT (1 << 4) + +#define APC_MODBUS_UPSCOMMAND_BF_REG 1536 +/* 0 - 2 are reserved */ +#define APC_MODBUS_UPSCOMMAND_BF_RESTORE_FACTORY_SETTINGS (1 << 3) +#define APC_MODBUS_UPSCOMMAND_BF_OUTPUT_INTO_BYPASS (1 << 4) +#define APC_MODBUS_UPSCOMMAND_BF_OUTPUT_OUT_OF_BYPASS (1 << 5) +/* 6 - 8 are reserved */ +#define APC_MODBUS_UPSCOMMAND_BF_CLEAR_FAULTS (1 << 9) +/* 10 - 12 are reserved */ +#define APC_MODBUS_UPSCOMMAND_BF_RESET_STRINGS (1 << 13) +#define APC_MODBUS_UPSCOMMAND_BF_RESET_LOGS (1 << 14) + +#define APC_MODBUS_OUTLETCOMMAND_BF_REG 1538 +#define APC_MODBUS_OUTLETCOMMAND_BF_CMD_CANCEL (1 << 0) +#define APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_ON (1 << 1) +#define APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_OFF (1 << 2) +#define APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_SHUTDOWN (1 << 3) +#define APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_REBOOT (1 << 4) +#define APC_MODBUS_OUTLETCOMMAND_BF_MOD_COLD_BOOT_ALLOWED (1 << 5) +#define APC_MODBUS_OUTLETCOMMAND_BF_MOD_USE_ON_DELAY (1 << 6) +#define APC_MODBUS_OUTLETCOMMAND_BF_MOD_USE_OFF_DELAY (1 << 7) +#define APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP (1 << 8) +#define APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_0 (1 << 9) +#define APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_1 (1 << 10) +#define APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_2 (1 << 11) +#define APC_MODBUS_OUTLETCOMMAND_BF_SOURCE_USB_PORT (1 << 12) +#define APC_MODBUS_OUTLETCOMMAND_BF_SOURCE_LOCAL_USER (1 << 13) +#define APC_MODBUS_OUTLETCOMMAND_BF_SOURCE_RJ45_PORT (1 << 14) +#define APC_MODBUS_OUTLETCOMMAND_BF_SOURCE_SMART_SLOT_1 (1 << 15) +#define APC_MODBUS_OUTLETCOMMAND_BF_SOURCE_SMART_SLOT_2 (1 << 16) +#define APC_MODBUS_OUTLETCOMMAND_BF_SOURCE_INTERNAL_NETWORK_1 (1 << 17) +#define APC_MODBUS_OUTLETCOMMAND_BF_SOURCE_INTERNAL_NETWORK_2 (1 << 18) + +#define APC_MODBUS_REPLACEBATTERYTESTCOMMAND_BF_REG 1541 +#define APC_MODBUS_REPLACEBATTERYTESTCOMMAND_BF_START (1 << 0) +#define APC_MODBUS_REPLACEBATTERYTESTCOMMAND_BF_ABORT (1 << 1) + +#define APC_MODBUS_RUNTIMECALIBRATIONCOMMAND_BF_REG 1542 +#define APC_MODBUS_RUNTIMECALIBRATIONCOMMAND_BF_START (1 << 0) +#define APC_MODBUS_RUNTIMECALIBRATIONCOMMAND_BF_ABORT (1 << 1) + +#define APC_MODBUS_USERINTERFACECOMMAND_BF_REG 1543 +#define APC_MODBUS_USERINTERFACECOMMAND_BF_SHORT_TEST (1 << 0) +#define APC_MODBUS_USERINTERFACECOMMAND_BF_CONTINUOUS_TEST (1 << 1) +#define APC_MODBUS_USERINTERFACECOMMAND_BF_MUTE_ALL_ACTIVE_AUDIBLE_ALARMS (1 << 2) +#define APC_MODBUS_USERINTERFACECOMMAND_BF_CANCEL_MUTE (1 << 3) +/* 4 is reserved */ +#define APC_MODBUS_USERINTERFACECOMMAND_BF_ACKNOWLEDGE_BATTERY_ALARMS (1 << 5) +#define APC_MODBUS_USERINTERFACECOMMAND_BF_ACKNOWLEDGE_SITE_WIRING_ALARM (1 << 6) + +#endif /* APC_MODBUS_H */ diff --git a/drivers/upshandler.h b/drivers/upshandler.h index fea10bc20c..78e6ffef0a 100644 --- a/drivers/upshandler.h +++ b/drivers/upshandler.h @@ -22,18 +22,20 @@ /* return values for instcmd */ enum { - STAT_INSTCMD_HANDLED = 0, /* completed successfully */ - STAT_INSTCMD_UNKNOWN, /* unspecified error */ - STAT_INSTCMD_INVALID, /* invalid command */ - STAT_INSTCMD_FAILED /* command failed */ + STAT_INSTCMD_HANDLED = 0, /* completed successfully */ + STAT_INSTCMD_UNKNOWN, /* unspecified error */ + STAT_INSTCMD_INVALID, /* invalid command */ + STAT_INSTCMD_FAILED, /* command failed */ + STAT_INSTCMD_CONVERSION_FAILED /* could not convert value */ }; /* return values for setvar */ enum { - STAT_SET_HANDLED = 0, /* completed successfully */ - STAT_SET_UNKNOWN, /* unspecified error */ - STAT_SET_INVALID, /* not writeable */ - STAT_SET_FAILED /* writing failed */ + STAT_SET_HANDLED = 0, /* completed successfully */ + STAT_SET_UNKNOWN, /* unspecified error */ + STAT_SET_INVALID, /* not writeable */ + STAT_SET_FAILED, /* writing failed */ + STAT_SET_CONVERSION_FAILED /* could not convert value from string */ }; /* structure for funcs that get called by msg parse routine */ diff --git a/include/timehead.h b/include/timehead.h index 7a5d989a7d..ed36d1b168 100644 --- a/include/timehead.h +++ b/include/timehead.h @@ -73,6 +73,14 @@ static inline struct tm *gmtime_r( const time_t *timer, struct tm *buf ) { # endif #endif +#ifndef HAVE_TIMEGM +# ifdef HAVE__MKGMTIME +# define timegm(tm) _mkgmtime(tm) +# else +# error "No fallback implementation for timegm" +# endif +#endif + #ifdef __cplusplus /* *INDENT-OFF* */ }