nut/drivers/generic_modbus.c
2022-06-29 12:37:36 +02:00

1099 lines
34 KiB
C

/* generic_modbus.c - Driver for generic UPS connected via modbus RIO
*
* Copyright (C)
* 2021 Dimitris Economou <dimitris.s.economou@gmail.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
*
*/
#include "main.h"
#include "generic_modbus.h"
#include <modbus.h>
#include <timehead.h>
#define DRIVER_NAME "NUT Generic Modbus driver"
#define DRIVER_VERSION "0.03"
/* variables */
static modbus_t *mbctx = NULL; /* modbus memory context */
static sigattr_t sigar[NUMOF_SIG_STATES]; /* array of ups signal attributes */
static int errcnt = 0; /* modbus access error counter */
static char *device_mfr = DEVICE_MFR; /* device manufacturer */
static char *device_model = DEVICE_MODEL; /* device model */
static int ser_baud_rate = BAUD_RATE; /* serial port baud rate */
static char ser_parity = PARITY; /* serial port parity */
static int ser_data_bit = DATA_BIT; /* serial port data bit */
static int ser_stop_bit = STOP_BIT; /* serial port stop bit */
static int rio_slave_id = MODBUS_SLAVE_ID; /* set device ID to default value */
static int FSD_pulse_duration = SHTDOWN_PULSE_DURATION; /* set the FSD pulse duration */
static uint32_t mod_resp_to_s = MODRESP_TIMEOUT_s; /* set the modbus response time out (s) */
static uint32_t mod_resp_to_us = MODRESP_TIMEOUT_us; /* set the modbus response time out (us) */
static uint32_t mod_byte_to_s = MODBYTE_TIMEOUT_s; /* set the modbus byte time out (us) */
static uint32_t mod_byte_to_us = MODBYTE_TIMEOUT_us; /* set the modbus byte time out (us) */
/* get config vars set by -x or defined in ups.conf driver section */
void get_config_vars(void);
/* create a new modbus context based on connection type (serial or TCP) */
modbus_t *modbus_new(const char *port);
/* reconnect upon communication error */
void modbus_reconnect(void);
/* modbus register read function */
int register_read(modbus_t *mb, int addr, regtype_t type, void *data);
/* instant command triggered by upsd */
int upscmd(const char *cmd, const char *arg);
/* read signal status */
int get_signal_state(devstate_t state);
/* count the time elapsed since start */
long time_elapsed(struct timeval *start);
int register_write(modbus_t *mb, int addr, regtype_t type, void *data);
/* driver description structure */
upsdrv_info_t upsdrv_info = {
DRIVER_NAME,
DRIVER_VERSION,
"Dimitris Economou <dimitris.s.economou@gmail.com>\n",
DRV_BETA,
{NULL}
};
/*
* driver functions
*/
/* initialize ups driver information */
void upsdrv_initinfo(void) {
upsdebugx(2, "upsdrv_initinfo");
/* set device information */
dstate_setinfo("device.mfr", "%s", device_mfr);
dstate_setinfo("device.model", "%s", device_model);
/* register instant commands */
if (sigar[FSD_T].addr != NOTUSED) {
dstate_addcmd("load.off");
}
/* set callback for instant commands */
upsh.instcmd = upscmd;
}
/* open serial connection and connect to modbus RIO */
void upsdrv_initups(void)
{
int rval;
upsdebugx(2, "upsdrv_initups");
get_config_vars();
/* open communication port */
mbctx = modbus_new(device_path);
if (mbctx == NULL) {
fatalx(EXIT_FAILURE, "modbus_new_rtu: Unable to open communication port context");
}
/* set slave ID */
rval = modbus_set_slave(mbctx, rio_slave_id);
if (rval < 0) {
modbus_free(mbctx);
fatalx(EXIT_FAILURE, "modbus_set_slave: Invalid modbus slave ID %d", rio_slave_id);
}
/* connect to modbus device */
if (modbus_connect(mbctx) == -1) {
modbus_free(mbctx);
fatalx(EXIT_FAILURE, "modbus_connect: unable to connect: error(%s)", modbus_strerror(errno));
}
/* set modbus response timeout */
#if (defined NUT_MODBUS_TIMEOUT_ARG_sec_usec_uint32) || (defined NUT_MODBUS_TIMEOUT_ARG_sec_usec_uint32_cast_timeval_fields)
rval = modbus_set_response_timeout(mbctx, mod_resp_to_s, mod_resp_to_us);
if (rval < 0) {
modbus_free(mbctx);
fatalx(EXIT_FAILURE, "modbus_set_response_timeout: error(%s)", modbus_strerror(errno));
}
#elif (defined NUT_MODBUS_TIMEOUT_ARG_timeval_numeric_fields)
{
/* Older libmodbus API (with timeval), and we have
* checked at configure time that we can put uint32_t
* into its fields. They are probably "long" on many
* systems as respectively time_t and suseconds_t -
* but that is not guaranteed; for more details see
* https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_time.h.html
*/
struct timeval to;
memset(&to, 0, sizeof(struct timeval));
to.tv_sec = mod_resp_to_s;
to.tv_usec = mod_resp_to_us;
/* void */ modbus_set_response_timeout(mbctx, &to);
}
/* #elif (defined NUT_MODBUS_TIMEOUT_ARG_timeval) // some un-castable type in fields */
#else
# error "Can not use libmodbus API for timeouts"
#endif /* NUT_MODBUS_TIMEOUT_ARG_* */
/* set modbus byte time out */
#if (defined NUT_MODBUS_TIMEOUT_ARG_sec_usec_uint32) || (defined NUT_MODBUS_TIMEOUT_ARG_sec_usec_uint32_cast_timeval_fields)
rval = modbus_set_byte_timeout(mbctx, mod_byte_to_s, mod_byte_to_us);
if (rval < 0) {
modbus_free(mbctx);
fatalx(EXIT_FAILURE, "modbus_set_byte_timeout: error(%s)", modbus_strerror(errno));
}
#elif (defined NUT_MODBUS_TIMEOUT_ARG_timeval_numeric_fields)
{ /* see comments above */
struct timeval to;
memset(&to, 0, sizeof(struct timeval));
to.tv_sec = mod_byte_to_s;
to.tv_usec = mod_byte_to_us;
/* void */ modbus_set_byte_timeout(mbctx, &to);
}
/* #elif (defined NUT_MODBUS_TIMEOUT_ARG_timeval) // some un-castable type in fields */
#endif /* NUT_MODBUS_TIMEOUT_ARG_* */
}
/* update UPS signal state */
void upsdrv_updateinfo(void)
{
int rval;
int online = -1; /* keep online state */
errcnt = 0;
upsdebugx(2, "upsdrv_updateinfo");
status_init(); /* initialize ups.status update */
alarm_init(); /* initialize ups.alarm update */
/*
* update UPS status regarding MAINS state either via OL | OB.
* if both statuses are mapped to contacts then only OL is evaluated.
*/
if (sigar[OL_T].addr != NOTUSED) {
rval = get_signal_state(OL_T);
upsdebugx(2, "OL value: %d", rval);
if (rval == -1) {
errcnt++;
} else if (rval == (1 ^ sigar[OL_T].noro)) {
status_set("OL");
online = 1;
} else {
status_set("OB");
online = 0;
/* if DISCHRG state is not mapped to a contact and UPS is on
* batteries set status to DISCHRG state */
if (sigar[DISCHRG_T].addr == NOTUSED) {
status_set("DISCHRG");
dstate_setinfo("battery.charger.status", "discharging");
}
}
} else if (sigar[OB_T].addr != NOTUSED) {
rval = get_signal_state(OB_T);
upsdebugx(2, "OB value: %d", rval);
if (rval == -1) {
errcnt++;
} else if (rval == (1 ^ sigar[OB_T].noro)) {
status_set("OB");
online = 0;
if (sigar[DISCHRG_T].addr == NOTUSED) {
status_set("DISCHRG");
dstate_setinfo("battery.charger.status", "discharging");
}
} else {
status_set("OL");
online = 1;
}
}
/*
* update UPS status regarding CHARGING state via HB. HB is usually
* mapped to "ready" contact when closed indicates a charging state > 85%
*/
if (sigar[HB_T].addr != NOTUSED) {
rval = get_signal_state(HB_T);
upsdebugx(2, "HB value: %d", rval);
if (rval == -1) {
errcnt++;
} else if (rval == (1 ^ sigar[HB_T].noro)) {
status_set("HB");
dstate_setinfo("battery.charger.status", "resting");
} else if (online == 1 && sigar[CHRG_T].addr == NOTUSED && errcnt == 0) {
status_set("CHRG");
dstate_setinfo("battery.charger.status", "charging");
} else if (online == 0 && sigar[DISCHRG_T].addr == NOTUSED && errcnt == 0) {
status_set("DISCHRG");
dstate_setinfo("battery.charger.status", "discharging");
}
}
/*
* update UPS status regarding DISCHARGING state via LB. LB is mapped
* to "battery low" contact.
*/
if (sigar[LB_T].addr != NOTUSED) {
rval = get_signal_state(LB_T);
upsdebugx(2, "LB value: %d", rval);
if (rval == -1) {
errcnt++;
} else if (rval == (1 ^ sigar[LB_T].noro)) {
status_set("LB");
alarm_set("Low Battery (Charge)");
}
}
/*
* update UPS status regarding battery HEALTH state via RB. RB is mapped
* to "replace battery" contact
*/
if (sigar[RB_T].addr != NOTUSED) {
rval = get_signal_state(RB_T);
upsdebugx(2, "RB value: %d", rval);
if (rval == -1) {
errcnt++;
} else if (rval == (1 ^ sigar[RB_T].noro)) {
status_set("RB");
alarm_set("Replace Battery");
}
}
/*
* update UPS status regarding battery HEALTH state via RB. RB is mapped
* to "replace battery" contact
*/
if (sigar[CHRG_T].addr != NOTUSED) {
rval = get_signal_state(CHRG_T);
upsdebugx(2, "CHRG value: %d", rval);
if (rval == -1) {
errcnt++;
} else if (rval == (1 ^ sigar[CHRG_T].noro)) {
status_set("CHRG");
dstate_setinfo("battery.charger.status", "charging");
}
} else if (sigar[DISCHRG_T].addr != NOTUSED) {
rval = get_signal_state(DISCHRG_T);
upsdebugx(2, "DISCHRG value: %d", rval);
if (rval == -1) {
errcnt++;
} else if (rval == (1 ^ sigar[DISCHRG_T].noro)) {
status_set("DISCHRG");
dstate_setinfo("battery.charger.status", "discharging");
}
}
/* check for communication errors */
if (errcnt == 0) {
alarm_commit();
status_commit();
dstate_dataok();
} else {
upsdebugx(2,"Communication errors: %d", errcnt);
dstate_datastale();
}
}
/* shutdown UPS */
void upsdrv_shutdown(void)
{
int rval;
int cnt = FSD_REPEAT_CNT; /* shutdown repeat counter */
struct timeval start;
long etime;
/* retry sending shutdown command on error */
while ((rval = upscmd("load.off", NULL)) != STAT_INSTCMD_HANDLED && cnt > 0) {
rval = gettimeofday(&start, NULL);
if (rval < 0) {
upslogx(LOG_ERR, "upscmd: gettimeofday: %s", strerror(errno));
}
/* wait for an increasing time interval before sending shutdown command */
while ((etime = time_elapsed(&start)) < ( FSD_REPEAT_INTRV / cnt));
upsdebugx(2,"ERROR: load.off failed, wait for %lims, retries left: %d\n", etime, cnt - 1);
cnt--;
}
switch (rval) {
case STAT_INSTCMD_FAILED:
case STAT_INSTCMD_INVALID:
fatalx(EXIT_FAILURE, "shutdown failed");
case STAT_INSTCMD_UNKNOWN:
fatalx(EXIT_FAILURE, "shutdown not supported");
default:
break;
}
upslogx(LOG_INFO, "shutdown command executed");
}
/* print driver usage info */
void upsdrv_help(void)
{
}
/* list flags and values that you want to receive via -x */
void upsdrv_makevartable(void)
{
addvar(VAR_VALUE, "device_mfr", "device manufacturer");
addvar(VAR_VALUE, "device_model", "device model");
addvar(VAR_VALUE, "ser_baud_rate", "serial port baud rate");
addvar(VAR_VALUE, "ser_parity", "serial port parity");
addvar(VAR_VALUE, "ser_data_bit", "serial port data bit");
addvar(VAR_VALUE, "ser_stop_bit", "serial port stop bit");
addvar(VAR_VALUE, "rio_slave_id", "RIO modbus slave ID");
addvar(VAR_VALUE, "mod_resp_to_s", "modbus response timeout (s)");
addvar(VAR_VALUE, "mod_resp_to_us", "modbus response timeout (us)");
addvar(VAR_VALUE, "mod_byte_to_s", "modbus byte timeout (s)");
addvar(VAR_VALUE, "mod_byte_to_us", "modbus byte timeout (us)");
addvar(VAR_VALUE, "OL_addr", "modbus address for OL state");
addvar(VAR_VALUE, "OB_addr", "modbus address for OB state");
addvar(VAR_VALUE, "LB_addr", "modbus address for LB state");
addvar(VAR_VALUE, "HB_addr", "modbus address for HB state");
addvar(VAR_VALUE, "RB_addr", "modbus address for RB state");
addvar(VAR_VALUE, "CHRG_addr", "modbus address for CHRG state");
addvar(VAR_VALUE, "DISCHRG_addr", "modbus address for DISCHRG state");
addvar(VAR_VALUE, "FSD_addr", "modbus address for FSD command");
addvar(VAR_VALUE, "OL_regtype", "modbus register type for OL state");
addvar(VAR_VALUE, "OB_regtype", "modbus register type for OB state");
addvar(VAR_VALUE, "LB_regtype", "modbus register type for LB state");
addvar(VAR_VALUE, "HB_regtype", "modbus register type for HB state");
addvar(VAR_VALUE, "RB_regtype", "modbus register type for RB state");
addvar(VAR_VALUE, "CHRG_regtype", "modbus register type for CHRG state");
addvar(VAR_VALUE, "DISCHRG_regtype", "modbus register type for DISCHRG state");
addvar(VAR_VALUE, "FSD_regtype", "modbus register type for FSD command");
addvar(VAR_VALUE, "OL_noro", "NO/NC configuration for OL state");
addvar(VAR_VALUE, "OB_noro", "NO/NC configuration for OB state");
addvar(VAR_VALUE, "LB_noro", "NO/NC configuration for LB state");
addvar(VAR_VALUE, "HB_noro", "NO/NC configuration for HB state");
addvar(VAR_VALUE, "RB_noro", "NO/NC configuration for RB state");
addvar(VAR_VALUE, "CHRG_noro", "NO/NC configuration for CHRG state");
addvar(VAR_VALUE, "DISCHRG_noro", "NO/NC configuration for DISCHRG state");
addvar(VAR_VALUE, "FSD_noro", "NO/NC configuration for FSD state");
addvar(VAR_VALUE, "FSD_pulse_duration", "FSD pulse duration");
}
/* close modbus connection and free modbus context allocated memory */
void upsdrv_cleanup(void)
{
if (mbctx != NULL) {
modbus_close(mbctx);
modbus_free(mbctx);
}
}
/*
* driver support functions
*/
/* Read a modbus register */
int register_read(modbus_t *mb, int addr, regtype_t type, void *data)
{
int rval = -1;
/* register bit masks */
uint mask8 = 0x000F;
uint mask16 = 0x00FF;
switch (type) {
case COIL:
rval = modbus_read_bits(mb, addr, 1, (uint8_t *)data);
*(uint *)data = *(uint *)data & mask8;
break;
case INPUT_B:
rval = modbus_read_input_bits(mb, addr, 1, (uint8_t *)data);
*(uint *)data = *(uint *)data & mask8;
break;
case INPUT_R:
rval = modbus_read_input_registers(mb, addr, 1, (uint16_t *)data);
*(uint *)data = *(uint *)data & mask16;
break;
case HOLDING:
rval = modbus_read_registers(mb, addr, 1, (uint16_t *)data);
*(uint *)data = *(uint *)data & mask16;
break;
#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) )
# pragma GCC diagnostic push
#endif
#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT
# pragma GCC diagnostic ignored "-Wcovered-switch-default"
#endif
#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE
# pragma GCC diagnostic ignored "-Wunreachable-code"
#endif
/* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */
#ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wunreachable-code"
# pragma clang diagnostic ignored "-Wcovered-switch-default"
#endif
/* All enum cases defined as of the time of coding
* have been covered above. Handle later definitions,
* memory corruptions and buggy inputs below...
*/
default:
upsdebugx(2,"ERROR: register_read: invalid register type %d\n", type);
break;
#ifdef __clang__
# pragma clang diagnostic pop
#endif
#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) )
# pragma GCC diagnostic pop
#endif
}
if (rval == -1) {
upslogx(LOG_ERR,"ERROR:(%s) modbus_read: addr:0x%x, type:%8s, path:%s\n",
modbus_strerror(errno),
addr,
(type == COIL) ? "COIL" :
(type == INPUT_B) ? "INPUT_B" :
(type == INPUT_R) ? "INPUT_R" : "HOLDING",
device_path
);
/* on BROKEN PIPE error try to reconnect */
if (errno == EPIPE) {
upsdebugx(2, "register_read: error(%s)", modbus_strerror(errno));
modbus_reconnect();
}
}
upsdebugx(3, "register addr: 0x%x, register type: %d read: %d",addr, type, *(uint *)data);
return rval;
}
/* write a modbus register */
int register_write(modbus_t *mb, int addr, regtype_t type, void *data)
{
int rval = -1;
/* register bit masks */
uint mask8 = 0x000F;
uint mask16 = 0x00FF;
switch (type) {
case COIL:
*(uint *)data = *(uint *)data & mask8;
rval = modbus_write_bit(mb, addr, *(uint8_t *)data);
break;
case HOLDING:
*(uint *)data = *(uint *)data & mask16;
rval = modbus_write_register(mb, addr, *(uint16_t *)data);
break;
case INPUT_B:
case INPUT_R:
#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcovered-switch-default"
#endif
/* All enum cases defined as of the time of coding
* have been covered above. Handle later definitions,
* memory corruptions and buggy inputs below...
*/
default:
#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT)
# pragma GCC diagnostic pop
#endif
upsdebugx(2,"ERROR: register_write: invalid register type %d\n", type);
break;
}
if (rval == -1) {
upslogx(LOG_ERR,"ERROR:(%s) modbus_read: addr:0x%x, type:%8s, path:%s\n",
modbus_strerror(errno),
addr,
(type == COIL) ? "COIL" :
(type == INPUT_B) ? "INPUT_B" :
(type == INPUT_R) ? "INPUT_R" : "HOLDING",
device_path
);
/* on BROKEN PIPE error try to reconnect */
if (errno == EPIPE) {
upsdebugx(2, "register_write: error(%s)", modbus_strerror(errno));
modbus_reconnect();
}
}
upsdebugx(3, "register addr: 0x%x, register type: %d read: %d",addr, type, *(uint *)data);
return rval;
}
/* returns the time elapsed since start in milliseconds */
long time_elapsed(struct timeval *start)
{
long rval;
struct timeval end;
rval = gettimeofday(&end, NULL);
if (rval < 0) {
upslogx(LOG_ERR, "time_elapsed: %s", strerror(errno));
}
if (start->tv_usec < end.tv_usec) {
suseconds_t nsec = (end.tv_usec - start->tv_usec) / 1000000 + 1;
end.tv_usec -= 1000000 * nsec;
end.tv_sec += nsec;
}
if (start->tv_usec - end.tv_usec > 1000000) {
suseconds_t nsec = (start->tv_usec - end.tv_usec) / 1000000;
end.tv_usec += 1000000 * nsec;
end.tv_sec -= nsec;
}
rval = (end.tv_sec - start->tv_sec) * 1000 + (end.tv_usec - start->tv_usec) / 1000;
return rval;
}
/* instant command triggered by upsd */
int upscmd(const char *cmd, const char *arg)
{
int rval;
int data;
struct timeval start;
long etime;
if (!strcasecmp(cmd, "load.off")) {
if (sigar[FSD_T].addr != NOTUSED &&
(sigar[FSD_T].type == COIL || sigar[FSD_T].type == HOLDING)
) {
data = 1 ^ sigar[FSD_T].noro;
rval = register_write(mbctx, sigar[FSD_T].addr, sigar[FSD_T].type, &data);
if (rval == -1) {
upslogx(2, "ERROR:(%s) modbus_write_register: addr:0x%08x, regtype: %d, path:%s\n",
modbus_strerror(errno),
sigar[FSD_T].addr,
sigar[FSD_T].type,
device_path
);
upslogx(LOG_NOTICE, "load.off: failed (communication error) [%s] [%s]", cmd, arg);
rval = STAT_INSTCMD_FAILED;
} else {
upsdebugx(2, "load.off: addr: 0x%x, data: %d", sigar[FSD_T].addr, data);
rval = STAT_INSTCMD_HANDLED;
}
/* if pulse has been defined and rising edge was successful */
if (FSD_pulse_duration != NOTUSED && rval == STAT_INSTCMD_HANDLED) {
rval = gettimeofday(&start, NULL);
if (rval < 0) {
upslogx(LOG_ERR, "upscmd: gettimeofday: %s", strerror(errno));
}
/* wait for FSD_pulse_duration ms */
while ((etime = time_elapsed(&start)) < FSD_pulse_duration);
data = 0 ^ sigar[FSD_T].noro;
rval = register_write(mbctx, sigar[FSD_T].addr, sigar[FSD_T].type, &data);
if (rval == -1) {
upslogx(LOG_ERR, "ERROR:(%s) modbus_write_register: addr:0x%08x, regtype: %d, path:%s\n",
modbus_strerror(errno),
sigar[FSD_T].addr,
sigar[FSD_T].type,
device_path
);
upslogx(LOG_NOTICE, "load.off: failed (communication error) [%s] [%s]", cmd, arg);
rval = STAT_INSTCMD_FAILED;
} else {
upsdebugx(2, "load.off: addr: 0x%x, data: %d, elapsed time: %lims",
sigar[FSD_T].addr,
data,
etime
);
rval = STAT_INSTCMD_HANDLED;
}
}
} else {
upslogx(LOG_NOTICE,"load.off: failed (FSD address undefined or invalid register type) [%s] [%s]",
cmd,
arg
);
rval = STAT_INSTCMD_FAILED;
}
} else {
upslogx(LOG_NOTICE, "instcmd: unknown command [%s] [%s]", cmd, arg);
rval = STAT_INSTCMD_UNKNOWN;
}
return rval;
}
/* read signal state from modbus RIO, returns 0|1 state or -1 on communication error */
int get_signal_state(devstate_t state)
{
int rval = -1;
int reg_val;
regtype_t rtype = 0; /* register type */
int addr = -1; /* register address */
/* assign register address and type */
switch (state) {
case OL_T:
addr = sigar[OL_T].addr;
rtype = sigar[OL_T].type;
break;
case OB_T:
addr = sigar[OB_T].addr;
rtype = sigar[OB_T].type;
break;
case LB_T:
addr = sigar[LB_T].addr;
rtype = sigar[LB_T].type;
break;
case HB_T:
addr = sigar[HB_T].addr;
rtype = sigar[HB_T].type;
break;
case RB_T:
addr = sigar[RB_T].addr;
rtype = sigar[RB_T].type;
break;
case CHRG_T:
addr = sigar[CHRG_T].addr;
rtype = sigar[CHRG_T].type;
break;
case DISCHRG_T:
addr = sigar[DISCHRG_T].addr;
rtype = sigar[DISCHRG_T].type;
break;
case BYPASS_T:
case CAL_T:
case FSD_T:
case OFF_T:
case OVER_T:
case TRIM_T:
case BOOST_T:
#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcovered-switch-default"
#endif
/* All enum cases defined as of the time of coding
* have been covered above. Handle later definitions,
* memory corruptions and buggy inputs below...
*/
default:
#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT)
# pragma GCC diagnostic pop
#endif
break;
}
rval = register_read(mbctx, addr, rtype, &reg_val);
if (rval > -1) {
rval = reg_val;
}
upsdebugx(3, "get_signal_state: state: %d", reg_val);
return rval;
}
/* get driver configuration parameters */
void get_config_vars()
{
int i; /* local index */
/* initialize sigar table */
for (i = 0; i < NUMOF_SIG_STATES; i++) {
sigar[i].addr = NOTUSED;
sigar[i].noro = 0; /* ON corresponds to 1 (closed contact) */
}
/* check if device manufacturer is set ang get the value */
if (testvar("device_mfr")) {
device_mfr = getval("device_mfr");
}
upsdebugx(2, "device_mfr %s", device_mfr);
/* check if device model is set ang get the value */
if (testvar("device_model")) {
device_model = getval("device_model");
}
upsdebugx(2, "device_model %s", device_model);
/* check if serial baud rate is set ang get the value */
if (testvar("ser_baud_rate")) {
ser_baud_rate = (int)strtol(getval("ser_baud_rate"), NULL, 10);
}
upsdebugx(2, "ser_baud_rate %d", ser_baud_rate);
/* check if serial parity is set ang get the value */
if (testvar("ser_parity")) {
/* Dereference the char* we get */
char *sp = getval("ser_parity");
if (sp) {
/* TODO? Sanity-check the char we get? */
ser_parity = *sp;
} else {
upsdebugx(2, "Could not determine ser_parity, will keep default");
}
}
upsdebugx(2, "ser_parity %c", ser_parity);
/* check if serial data bit is set ang get the value */
if (testvar("ser_data_bit")) {
ser_data_bit = (int)strtol(getval("ser_data_bit"), NULL, 10);
}
upsdebugx(2, "ser_data_bit %d", ser_data_bit);
/* check if serial stop bit is set ang get the value */
if (testvar("ser_stop_bit")) {
ser_stop_bit = (int)strtol(getval("ser_stop_bit"), NULL, 10);
}
upsdebugx(2, "ser_stop_bit %d", ser_stop_bit);
/* check if device ID is set ang get the value */
if (testvar("rio_slave_id")) {
rio_slave_id = (int)strtol(getval("rio_slave_id"), NULL, 10);
}
upsdebugx(2, "rio_slave_id %d", rio_slave_id);
/* check if response time out (s) is set ang get the value */
if (testvar("mod_resp_to_s")) {
mod_resp_to_s = (uint32_t)strtol(getval("mod_resp_to_s"), NULL, 10);
}
upsdebugx(2, "mod_resp_to_s %d", mod_resp_to_s);
/* check if response time out (us) is set ang get the value */
if (testvar("mod_resp_to_us")) {
mod_resp_to_us = (uint32_t) strtol(getval("mod_resp_to_us"), NULL, 10);
if (mod_resp_to_us > 999999) {
fatalx(EXIT_FAILURE, "get_config_vars: Invalid mod_resp_to_us %d", mod_resp_to_us);
}
}
upsdebugx(2, "mod_resp_to_us %d", mod_resp_to_us);
/* check if byte time out (s) is set ang get the value */
if (testvar("mod_byte_to_s")) {
mod_byte_to_s = (uint32_t)strtol(getval("mod_byte_to_s"), NULL, 10);
}
upsdebugx(2, "mod_byte_to_s %d", mod_byte_to_s);
/* check if byte time out (us) is set ang get the value */
if (testvar("mod_byte_to_us")) {
mod_byte_to_us = (uint32_t) strtol(getval("mod_byte_to_us"), NULL, 10);
if (mod_byte_to_us > 999999) {
fatalx(EXIT_FAILURE, "get_config_vars: Invalid mod_byte_to_us %d", mod_byte_to_us);
}
}
upsdebugx(2, "mod_byte_to_us %d", mod_byte_to_us);
/* check if OL address is set and get the value */
if (testvar("OL_addr")) {
sigar[OL_T].addr = (int)strtol(getval("OL_addr"), NULL, 0);
if (testvar("OL_noro")) {
sigar[OL_T].noro = (int)strtol(getval("OL_noro"), NULL, 10);
if (sigar[OL_T].noro != 1) {
sigar[OL_T].noro = 0;
}
}
}
/* check if OL register type is set and get the value otherwise set to INPUT_B */
if (testvar("OL_regtype")) {
sigar[OL_T].type = (unsigned int)strtol(getval("OL_regtype"), NULL, 10);
if (sigar[OL_T].type < COIL || sigar[OL_T].type > HOLDING) {
sigar[OL_T].type = INPUT_B;
}
} else {
sigar[OL_T].type = INPUT_B;
}
/* check if OB address is set and get the value */
if (testvar("OB_addr")) {
sigar[OB_T].addr = (int)strtol(getval("OB_addr"), NULL, 0);
}
if (testvar("OB_noro")) {
sigar[OB_T].noro = (int)strtol(getval("OB_noro"), NULL, 10);
if (sigar[OB_T].noro != 1) {
sigar[OB_T].noro = 0;
}
}
/* check if OB register type is set and get the value otherwise set to INPUT_B */
if (testvar("OB_regtype")) {
sigar[OB_T].type = (unsigned int)strtol(getval("OB_regtype"), NULL, 10);
if (sigar[OB_T].type < COIL || sigar[OB_T].type > HOLDING) {
sigar[OB_T].type = INPUT_B;
}
} else {
sigar[OB_T].type = INPUT_B;
}
/* check if LB address is set and get the value */
if (testvar("LB_addr")) {
sigar[LB_T].addr = (int)strtol(getval("LB_addr"), NULL, 0);
if (testvar("LB_noro")) {
sigar[LB_T].noro = (int)strtol(getval("LB_noro"), NULL, 10);
if (sigar[LB_T].noro != 1) {
sigar[LB_T].noro = 0;
}
}
}
/* check if LB register type is set and get the value otherwise set to INPUT_B */
if (testvar("LB_regtype")) {
sigar[LB_T].type = (unsigned int)strtol(getval("OB_regtype"), NULL, 10);
if (sigar[LB_T].type < COIL || sigar[LB_T].type > HOLDING) {
sigar[LB_T].type = INPUT_B;
}
} else {
sigar[LB_T].type = INPUT_B;
}
/* check if HB address is set and get the value */
if (testvar("HB_addr")) {
sigar[HB_T].addr = (int)strtol(getval("HB_addr"), NULL, 0);
if (testvar("HB_noro")) {
sigar[HB_T].noro = (int)strtol(getval("HB_noro"), NULL, 10);
if (sigar[HB_T].noro != 1) {
sigar[HB_T].noro = 0;
}
}
}
/* check if HB register type is set and get the value otherwise set to INPUT_B */
if (testvar("HB_regtype")) {
sigar[HB_T].type = (unsigned int)strtol(getval("HB_regtype"), NULL, 10);
if (sigar[HB_T].type < COIL || sigar[HB_T].type > HOLDING) {
sigar[HB_T].type = INPUT_B;
}
} else {
sigar[HB_T].type = INPUT_B;
}
/* check if RB address is set and get the value */
if (testvar("RB_addr")) {
sigar[RB_T].addr = (int)strtol(getval("RB_addr"), NULL, 0);
if (testvar("RB_noro")) {
sigar[RB_T].noro = (int)strtol(getval("RB_noro"), NULL, 10);
if (sigar[RB_T].noro != 1) {
sigar[RB_T].noro = 0;
}
}
}
/* check if RB register type is set and get the value otherwise set to INPUT_B */
if (testvar("RB_regtype")) {
sigar[RB_T].type = (unsigned int)strtol(getval("RB_regtype"), NULL, 10);
if (sigar[RB_T].type < COIL || sigar[RB_T].type > HOLDING) {
sigar[RB_T].type = INPUT_B;
}
} else {
sigar[RB_T].type = INPUT_B;
}
/* check if CHRG address is set and get the value */
if (testvar("CHRG_addr")) {
sigar[CHRG_T].addr = (int)strtol(getval("CHRG_addr"), NULL, 0);
if (testvar("CHRG_noro")) {
sigar[CHRG_T].noro = (int)strtol(getval("CHRG_noro"), NULL, 10);
if (sigar[CHRG_T].noro != 1) {
sigar[CHRG_T].noro = 0;
}
}
}
/* check if CHRG register type is set and get the value otherwise set to INPUT_B */
if (testvar("CHRG_regtype")) {
sigar[CHRG_T].type = (unsigned int)strtol(getval("CHRG_regtype"), NULL, 10);
if (sigar[CHRG_T].type < COIL || sigar[CHRG_T].type > HOLDING) {
sigar[CHRG_T].type = INPUT_B;
}
} else {
sigar[CHRG_T].type = INPUT_B;
}
/* check if DISCHRG address is set and get the value */
if (testvar("DISCHRG_addr")) {
sigar[DISCHRG_T].addr = (int)strtol(getval("DISCHRG_addr"), NULL, 0);
if (testvar("DISCHRG_noro")) {
sigar[DISCHRG_T].noro = (int)strtol(getval("DISCHRG_noro"), NULL, 10);
if (sigar[DISCHRG_T].noro != 1) {
sigar[DISCHRG_T].noro = 0;
}
}
}
/* check if DISCHRG register type is set and get the value otherwise set to INPUT_B */
if (testvar("DISCHRG_regtype")) {
sigar[DISCHRG_T].type = (unsigned int)strtol(getval("DISCHRG_regtype"), NULL, 10);
if (sigar[DISCHRG_T].type < COIL || sigar[DISCHRG_T].type > HOLDING) {
sigar[DISCHRG_T].type = INPUT_B;
}
} else {
sigar[DISCHRG_T].type = INPUT_B;
}
/* check if FSD address is set and get the value */
if (testvar("FSD_addr")) {
sigar[FSD_T].addr = (int)strtol(getval("FSD_addr"), NULL, 0);
if (testvar("FSD_noro")) {
sigar[FSD_T].noro = (int)strtol(getval("FSD_noro"), NULL, 10);
if (sigar[FSD_T].noro != 1) {
sigar[FSD_T].noro = 0;
}
}
}
/* check if FSD register type is set and get the value otherwise set to COIL */
if (testvar("FSD_regtype")) {
sigar[FSD_T].type = (unsigned int)strtol(getval("FSD_regtype"), NULL, 10);
if (sigar[FSD_T].type < COIL || sigar[FSD_T].type > HOLDING) {
sigar[FSD_T].type = COIL;
}
} else {
sigar[FSD_T].type = COIL;
}
/* check if FSD pulse duration is set and get the value */
if (testvar("FSD_pulse_duration")) {
FSD_pulse_duration = (int) strtol(getval("FSD_pulse_duration"), NULL, 10);
}
upsdebugx(2, "FSD_pulse_duration %d", FSD_pulse_duration);
/* debug loop over signal array */
for (i = 0; i < NUMOF_SIG_STATES; i++) {
if (sigar[i].addr != NOTUSED) {
char *signame;
switch (i) {
case OL_T:
signame = "OL";
break;
case OB_T:
signame = "OB";
break;
case LB_T:
signame = "LB";
break;
case HB_T:
signame = "HB";
break;
case RB_T:
signame = "RB";
break;
case FSD_T:
signame = "FSD";
break;
case CHRG_T:
signame = "CHRG";
break;
case DISCHRG_T:
signame = "DISCHRG";
break;
default:
signame = "NOTUSED";
break;
}
upsdebugx(2, "%s, addr:0x%x, type:%d", signame, sigar[i].addr, sigar[i].type);
}
}
}
/* create a new modbus context based on connection type (serial or TCP) */
modbus_t *modbus_new(const char *port)
{
modbus_t *mb;
char *sp;
if (strstr(port, "/dev/tty") != NULL) {
mb = modbus_new_rtu(port, ser_baud_rate, ser_parity, ser_data_bit, ser_stop_bit);
if (mb == NULL) {
upslogx(LOG_ERR, "modbus_new_rtu: Unable to open serial port context\n");
}
} else if ((sp = strchr(port, ':')) != NULL) {
char *tcp_port = xmalloc(sizeof(sp));
strcpy(tcp_port, sp + 1);
*sp = '\0';
mb = modbus_new_tcp(port, (int)strtoul(tcp_port, NULL, 10));
if (mb == NULL) {
upslogx(LOG_ERR, "modbus_new_tcp: Unable to connect to %s\n", port);
}
free(tcp_port);
} else {
mb = modbus_new_tcp(port, 502);
if (mb == NULL) {
upslogx(LOG_ERR, "modbus_new_tcp: Unable to connect to %s\n", port);
}
}
return mb;
}
/* reconnect to modbus server upon connection error */
void modbus_reconnect(void)
{
int rval;
upsdebugx(2, "modbus_reconnect, trying to reconnect to modbus server");
/* clear current modbus context */
modbus_close(mbctx);
modbus_free(mbctx);
/* open communication port */
mbctx = modbus_new(device_path);
if (mbctx == NULL) {
fatalx(EXIT_FAILURE, "modbus_new_rtu: Unable to open communication port context");
}
/* set slave ID */
rval = modbus_set_slave(mbctx, rio_slave_id);
if (rval < 0) {
modbus_free(mbctx);
fatalx(EXIT_FAILURE, "modbus_set_slave: Invalid modbus slave ID %d", rio_slave_id);
}
/* connect to modbus device */
if (modbus_connect(mbctx) == -1) {
modbus_free(mbctx);
fatalx(EXIT_FAILURE, "modbus_connect: unable to connect: %s", modbus_strerror(errno));
}
/* set modbus response timeout */
#if (defined NUT_MODBUS_TIMEOUT_ARG_sec_usec_uint32) || (defined NUT_MODBUS_TIMEOUT_ARG_sec_usec_uint32_cast_timeval_fields)
rval = modbus_set_response_timeout(mbctx, mod_resp_to_s, mod_resp_to_us);
if (rval < 0) {
modbus_free(mbctx);
fatalx(EXIT_FAILURE, "modbus_set_response_timeout: error(%s)", modbus_strerror(errno));
}
#elif (defined NUT_MODBUS_TIMEOUT_ARG_timeval_numeric_fields)
{ /* see comments above */
struct timeval to;
memset(&to, 0, sizeof(struct timeval));
to.tv_sec = mod_resp_to_s;
to.tv_usec = mod_resp_to_us;
/* void */ modbus_set_response_timeout(mbctx, &to);
}
/* #elif (defined NUT_MODBUS_TIMEOUT_ARG_timeval) // some un-castable type in fields */
#endif /* NUT_MODBUS_TIMEOUT_ARG_* */
/* set modbus byte timeout */
#if (defined NUT_MODBUS_TIMEOUT_ARG_sec_usec_uint32) || (defined NUT_MODBUS_TIMEOUT_ARG_sec_usec_uint32_cast_timeval_fields)
rval = modbus_set_byte_timeout(mbctx, mod_byte_to_s, mod_byte_to_us);
if (rval < 0) {
modbus_free(mbctx);
fatalx(EXIT_FAILURE, "modbus_set_byte_timeout: error(%s)", modbus_strerror(errno));
}
#elif (defined NUT_MODBUS_TIMEOUT_ARG_timeval_numeric_fields)
{ /* see comments above */
struct timeval to;
memset(&to, 0, sizeof(struct timeval));
to.tv_sec = mod_byte_to_s;
to.tv_usec = mod_byte_to_us;
/* void */ modbus_set_byte_timeout(mbctx, &to);
}
/* #elif (defined NUT_MODBUS_TIMEOUT_ARG_timeval) // some un-castable type in fields */
#endif /* NUT_MODBUS_TIMEOUT_ARG_* */
}