/* generic_modbus.c - Driver for generic UPS connected via modbus RIO * * Copyright (C) * 2021 Dimitris Economou * * 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 #include #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 \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, ®_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_* */ }