1345 lines
39 KiB
C
1345 lines
39 KiB
C
|
/* adelsystem_cbi.c - driver for ADELSYSTEM CB/CBI DC-UPS
|
||
|
*
|
||
|
* Copyright (C)
|
||
|
* 2022 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
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* code indentation with tabstop=4
|
||
|
*/
|
||
|
|
||
|
#include "main.h"
|
||
|
#include "adelsystem_cbi.h"
|
||
|
#include <modbus.h>
|
||
|
#include <timehead.h>
|
||
|
|
||
|
#define DRIVER_NAME "NUT ADELSYSTEM DC-UPS CB/CBI driver"
|
||
|
#define DRIVER_VERSION "0.01"
|
||
|
|
||
|
/* variables */
|
||
|
static modbus_t *mbctx = NULL; /* modbus memory context */
|
||
|
static devstate_t *dstate = NULL; /* device state context */
|
||
|
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 char *device_type = DEVICE_TYPE; /* 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 dev_slave_id = MODBUS_SLAVE_ID; /* set device ID to default value */
|
||
|
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) */
|
||
|
|
||
|
|
||
|
/* initialize alarm structs */
|
||
|
void alrminit(void);
|
||
|
|
||
|
/* initialize register start address and hex address from register number */
|
||
|
void reginit(void);
|
||
|
|
||
|
/* read registers' memory region */
|
||
|
int read_all_regs(modbus_t *mb, uint16_t *data);
|
||
|
|
||
|
/* get config vars set by -x or defined in ups.conf driver section */
|
||
|
void get_config_vars(void);
|
||
|
|
||
|
/* get device state */
|
||
|
int get_dev_state(devreg_t regindx, devstate_t **dvstat);
|
||
|
|
||
|
/* 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);
|
||
|
|
||
|
/* modbus register write function */
|
||
|
int register_write(modbus_t *mb, int addr, regtype_t type, void *data);
|
||
|
|
||
|
/* instant command triggered by upsd */
|
||
|
int upscmd(const char *cmd, const char *arg);
|
||
|
|
||
|
/* count the time elapsed since start */
|
||
|
long time_elapsed(struct timeval *start);
|
||
|
|
||
|
|
||
|
/* 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
|
||
|
*/
|
||
|
|
||
|
/* read configuration variables from ups.conf and connect to ups device */
|
||
|
void upsdrv_initups(void)
|
||
|
{
|
||
|
int rval;
|
||
|
upsdebugx(2, "upsdrv_initups");
|
||
|
|
||
|
dstate = (devstate_t *)xmalloc(sizeof(devstate_t));
|
||
|
alrminit();
|
||
|
reginit();
|
||
|
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, dev_slave_id);
|
||
|
if (rval < 0) {
|
||
|
modbus_free(mbctx);
|
||
|
fatalx(EXIT_FAILURE, "modbus_set_slave: Invalid modbus slave ID %d", dev_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_* */
|
||
|
|
||
|
}
|
||
|
|
||
|
/* initialize ups driver information */
|
||
|
void upsdrv_initinfo(void)
|
||
|
{
|
||
|
devstate_t *ds = dstate; /* device state context */
|
||
|
upsdebugx(2, "upsdrv_initinfo");
|
||
|
|
||
|
/* set device information */
|
||
|
dstate_setinfo("device.mfr", "%s", device_mfr);
|
||
|
dstate_setinfo("device.model", "%s", device_model);
|
||
|
dstate_setinfo("device.type", "%s", device_type);
|
||
|
|
||
|
/* read ups model */
|
||
|
get_dev_state(PRDN, &ds);
|
||
|
dstate_setinfo("ups.model", "%s", ds->product.name);
|
||
|
upslogx(LOG_INFO, "ups.model = %s", ds->product.name);
|
||
|
|
||
|
/* register instant commands */
|
||
|
dstate_addcmd("load.off");
|
||
|
|
||
|
/* set callback for instant commands */
|
||
|
upsh.instcmd = upscmd;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* update UPS signal state */
|
||
|
void upsdrv_updateinfo(void)
|
||
|
{
|
||
|
int rval; /* return value */
|
||
|
int i; /* local index */
|
||
|
devstate_t *ds = dstate; /* device state */
|
||
|
|
||
|
upsdebugx(2, "upsdrv_updateinfo");
|
||
|
|
||
|
errcnt = 0; /* initialize error counter to zero */
|
||
|
status_init(); /* initialize ups.status update */
|
||
|
alarm_init(); /* initialize ups.alarm update */
|
||
|
#if READALL_REGS == 1
|
||
|
rval = read_all_regs(mbctx, regs_data);
|
||
|
if (rval == -1) {
|
||
|
errcnt++;
|
||
|
} else {
|
||
|
#endif
|
||
|
/*
|
||
|
* update UPS status regarding MAINS and SHUTDOWN request
|
||
|
* - OL: On line (mains is present)
|
||
|
* - OB: On battery (mains is not present)
|
||
|
*/
|
||
|
rval = get_dev_state(MAIN, &ds);
|
||
|
if (rval == -1) {
|
||
|
errcnt++;
|
||
|
} else {
|
||
|
if (ds->alrm->alrm[MAINS_AVAIL_I].actv) {
|
||
|
status_set("OB");
|
||
|
alarm_set(mains->alrm[MAINS_AVAIL_I].descr);
|
||
|
upslogx(LOG_INFO, "ups.status = OB");
|
||
|
} else {
|
||
|
status_set("OL");
|
||
|
upslogx(LOG_INFO, "ups.status = OL");
|
||
|
}
|
||
|
if (ds->alrm->alrm[SHUTD_REQST_I].actv) {
|
||
|
status_set("FSD");
|
||
|
alarm_set(mains->alrm[SHUTD_REQST_I].descr);
|
||
|
upslogx(LOG_INFO, "ups.status = FSD");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* update UPS status regarding battery voltage
|
||
|
*/
|
||
|
rval = get_dev_state(BVAL, &ds);
|
||
|
if (rval == -1) {
|
||
|
errcnt++;
|
||
|
} else {
|
||
|
if (ds->alrm->alrm[BVAL_LOALRM_I].actv) {
|
||
|
status_set("LB");
|
||
|
alarm_set(bval->alrm[BVAL_LOALRM_I].descr);
|
||
|
upslogx(LOG_INFO, "ups.status = LB");
|
||
|
}
|
||
|
if (ds->alrm->alrm[BVAL_HIALRM_I].actv) {
|
||
|
status_set("HB");
|
||
|
alarm_set(bval->alrm[BVAL_HIALRM_I].descr);
|
||
|
upslogx(LOG_INFO, "ups.status = HB");
|
||
|
}
|
||
|
if (ds->alrm->alrm[BVAL_BSTSFL_I].actv) {
|
||
|
alarm_set(bval->alrm[BVAL_BSTSFL_I].descr);
|
||
|
upslogx(LOG_INFO, "battery start with battery flat");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* get "battery.voltage" */
|
||
|
rval = get_dev_state(BATV, &ds);
|
||
|
if (rval == -1) {
|
||
|
errcnt++;
|
||
|
} else {
|
||
|
dstate_setinfo("battery.voltage", "%s", ds->reg.strval);
|
||
|
upslogx(LOG_DEBUG, "battery.voltage = %s", ds->reg.strval);
|
||
|
}
|
||
|
/*
|
||
|
* update UPS status regarding battery charger status
|
||
|
*/
|
||
|
|
||
|
/* get "battery.charger.status" */
|
||
|
rval = get_dev_state(CHRG, &ds);
|
||
|
if (rval == -1) {
|
||
|
errcnt++;
|
||
|
} else {
|
||
|
if (ds->charge.state == CHRG_BULK || ds->charge.state == CHRG_ABSR) {
|
||
|
status_set("CHRG");
|
||
|
upslogx(LOG_INFO, "ups.status = CHRG");
|
||
|
}
|
||
|
dstate_setinfo("battery.charger.status", "%s", ds->charge.info);
|
||
|
upslogx(LOG_DEBUG, "battery.charger.status = %s", ds->charge.info);
|
||
|
}
|
||
|
rval = get_dev_state(PMNG, &ds);
|
||
|
if (rval == -1) {
|
||
|
errcnt++;
|
||
|
} else {
|
||
|
if (ds->power.state == PMNG_BCKUP) {
|
||
|
status_set("DISCHRG");
|
||
|
dstate_setinfo("battery.charger.status", "discharging");
|
||
|
upslogx(LOG_INFO, "ups.status = DISCHRG");
|
||
|
}
|
||
|
if (ds->power.state == PMNG_BOOST) {
|
||
|
status_set("BOOST");
|
||
|
upslogx(LOG_INFO, "ups.status = BOOST");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* update UPS battery state of charge
|
||
|
*/
|
||
|
rval = get_dev_state(BSOC, &ds);
|
||
|
if (rval == -1) {
|
||
|
errcnt++;
|
||
|
} else {
|
||
|
dstate_setinfo("battery.charge", "%s", ds->reg.strval);
|
||
|
upslogx(LOG_DEBUG, "battery.charge = %s", ds->reg.strval);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* update UPS AC input state
|
||
|
*/
|
||
|
rval = get_dev_state(VACA, &ds);
|
||
|
if (rval == -1) {
|
||
|
errcnt++;
|
||
|
} else {
|
||
|
for (i = 0; i < ds->alrm->alrm_c; i++) {
|
||
|
if (ds->alrm->alrm[i].actv) {
|
||
|
alarm_set(ds->alrm->alrm[i].descr);
|
||
|
upsdebugx(3, "%s is active", ds->alrm->alrm[i].descr);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
rval = get_dev_state(VAC, &ds);
|
||
|
if (rval == -1) {
|
||
|
errcnt++;
|
||
|
} else {
|
||
|
dstate_setinfo("input.voltage", "%s", ds->reg.strval);
|
||
|
upslogx(LOG_DEBUG, "input.voltage = %s", ds->reg.strval);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* update UPS onboard temperature state
|
||
|
*/
|
||
|
rval = get_dev_state(OBTA, &ds);
|
||
|
if (rval == -1) {
|
||
|
errcnt++;
|
||
|
} else {
|
||
|
for (i = 0; i < ds->alrm->alrm_c; i++) {
|
||
|
if (ds->alrm->alrm[i].actv) {
|
||
|
alarm_set(ds->alrm->alrm[i].descr);
|
||
|
upsdebugx(3, "%s is active", ds->alrm->alrm[i].descr);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
rval = get_dev_state(OTMP, &ds);
|
||
|
if (rval == -1) {
|
||
|
errcnt++;
|
||
|
} else {
|
||
|
dstate_setinfo("ups.temperature", "%s", ds->reg.strval);
|
||
|
upslogx(LOG_DEBUG, "ups.temperature = %s", ds->reg.strval);
|
||
|
}
|
||
|
/*
|
||
|
* update UPS battery temperature state
|
||
|
*/
|
||
|
rval = get_dev_state(BSTA, &ds);
|
||
|
if (rval == -1) {
|
||
|
errcnt++;
|
||
|
} else {
|
||
|
for (i = 0; i < ds->alrm->alrm_c; i++) {
|
||
|
if (ds->alrm->alrm[i].actv) {
|
||
|
alarm_set(ds->alrm->alrm[i].descr);
|
||
|
upsdebugx(3, "%s alarm is active", ds->alrm->alrm[i].descr);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
rval = get_dev_state(BTMP, &ds);
|
||
|
if (rval == -1) {
|
||
|
errcnt++;
|
||
|
} else {
|
||
|
dstate_setinfo("battery.temperature", "%s", ds->reg.strval);
|
||
|
upslogx(LOG_DEBUG, "battery.temperature = %s", ds->reg.strval);
|
||
|
}
|
||
|
rval = get_dev_state(TBUF, &ds);
|
||
|
if (rval == -1) {
|
||
|
errcnt++;
|
||
|
} else {
|
||
|
dstate_setinfo("battery.runtime", "%s", ds->reg.strval);
|
||
|
upslogx(LOG_DEBUG, "battery.runtime = %s", ds->reg.strval);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* update UPS device failure state
|
||
|
*/
|
||
|
rval = get_dev_state(DEVF, &ds);
|
||
|
if (rval == -1) {
|
||
|
errcnt++;
|
||
|
} else {
|
||
|
for (i = 0; i < ds->alrm->alrm_c; i++) {
|
||
|
if (ds->alrm->alrm[i].actv) {
|
||
|
alarm_set(ds->alrm->alrm[i].descr);
|
||
|
upsdebugx(3, "%s alarm is active", ds->alrm->alrm[i].descr);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* update UPS SoH and SoC states
|
||
|
*/
|
||
|
rval = get_dev_state(SCSH, &ds);
|
||
|
if (rval == -1) {
|
||
|
errcnt++;
|
||
|
} else {
|
||
|
for (i = 0; i < ds->alrm->alrm_c; i++) {
|
||
|
if (ds->alrm->alrm[i].actv) {
|
||
|
alarm_set(ds->alrm->alrm[i].descr);
|
||
|
upsdebugx(3, "%s alarm is active", ds->alrm->alrm[i].descr);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* update UPS battery state
|
||
|
*/
|
||
|
rval = get_dev_state(BSTA, &ds);
|
||
|
if (rval == -1) {
|
||
|
errcnt++;
|
||
|
} else {
|
||
|
for (i = 0; i < ds->alrm->alrm_c; i++) {
|
||
|
if (ds->alrm->alrm[i].actv) {
|
||
|
alarm_set(ds->alrm->alrm[i].descr);
|
||
|
upsdebugx(3, "%s alarm is active", ds->alrm->alrm[i].descr);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* update UPS load status
|
||
|
*/
|
||
|
rval = get_dev_state(LVDC, &ds);
|
||
|
if (rval == -1) {
|
||
|
errcnt++;
|
||
|
} else {
|
||
|
dstate_setinfo("output.voltage", "%s", ds->reg.strval);
|
||
|
upslogx(LOG_DEBUG, "output.voltage = %s", ds->reg.strval);
|
||
|
}
|
||
|
rval = get_dev_state(LCUR, &ds);
|
||
|
if (rval == -1) {
|
||
|
errcnt++;
|
||
|
} else {
|
||
|
dstate_setinfo("output.current", "%s", ds->reg.strval);
|
||
|
upslogx(LOG_DEBUG, "output.current = %s", ds->reg.strval);
|
||
|
}
|
||
|
#if READALL_REGS == 1
|
||
|
}
|
||
|
#endif
|
||
|
/* 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, "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, "dev_slave_id", "device 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)");
|
||
|
}
|
||
|
|
||
|
/* close modbus connection and free modbus context allocated memory */
|
||
|
void upsdrv_cleanup(void)
|
||
|
{
|
||
|
if (mbctx != NULL) {
|
||
|
modbus_close(mbctx);
|
||
|
modbus_free(mbctx);
|
||
|
}
|
||
|
if (dstate != NULL) {
|
||
|
free(dstate);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* driver support functions
|
||
|
*/
|
||
|
|
||
|
/* initialize alarm structs */
|
||
|
void alrminit(void)
|
||
|
{
|
||
|
mains = alloc_alrm_ar(mains_c, sizeof(mains_ar));
|
||
|
alrm_ar_init(mains, mains_ar, mains_c);
|
||
|
vaca = alloc_alrm_ar(vaca_c, sizeof(vaca_ar));
|
||
|
alrm_ar_init(vaca, vaca_ar, vaca_c);
|
||
|
devf = alloc_alrm_ar(devf_c, sizeof(devf_ar));
|
||
|
alrm_ar_init(devf, devf_ar, devf_c);
|
||
|
btsf = alloc_alrm_ar(btsf_c, sizeof(btsf_ar));
|
||
|
alrm_ar_init(btsf, btsf_ar, btsf_c);
|
||
|
bval = alloc_alrm_ar(bval_c, sizeof(bval_ar));
|
||
|
alrm_ar_init(bval, bval_ar, bval_c);
|
||
|
shsc = alloc_alrm_ar(shsc_c, sizeof(shsc_ar));
|
||
|
alrm_ar_init(shsc, shsc_ar, shsc_c);
|
||
|
bsta = alloc_alrm_ar(bsta_c, sizeof(bsta_ar));
|
||
|
alrm_ar_init(bsta, bsta_ar, bsta_c);
|
||
|
obta = alloc_alrm_ar(obta_c, sizeof(obta_ar));
|
||
|
alrm_ar_init(obta, obta_ar, obta_c);
|
||
|
}
|
||
|
|
||
|
/* initialize register start address and hex address from register number */
|
||
|
void reginit(void)
|
||
|
{
|
||
|
int i; /* local index */
|
||
|
|
||
|
for (i = 0; i < MODBUS_NUMOF_REGS; i++) {
|
||
|
int rnum = regs[i].num;
|
||
|
switch (regs[i].type) {
|
||
|
case COIL:
|
||
|
regs[i].saddr = rnum - 1;
|
||
|
regs[i].xaddr = 0x0 + regs[i].num - 1;
|
||
|
break;
|
||
|
case INPUT_B:
|
||
|
rnum -= 10000;
|
||
|
regs[i].saddr = rnum - 1;
|
||
|
regs[i].xaddr = 0x10000 + rnum - 1;
|
||
|
break;
|
||
|
case INPUT_R:
|
||
|
rnum -= 30000;
|
||
|
regs[i].saddr = rnum - 1;
|
||
|
regs[i].xaddr = 0x30000 + rnum - 1;
|
||
|
break;
|
||
|
case HOLDING:
|
||
|
rnum -= 40000;
|
||
|
regs[i].saddr = rnum - 1;
|
||
|
regs[i].xaddr = 0x40000 + rnum - 1;
|
||
|
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:
|
||
|
upslogx(LOG_ERR,
|
||
|
"Invalid register type %d for register %d",
|
||
|
regs[i].type,
|
||
|
regs[i].num
|
||
|
);
|
||
|
upsdebugx(3,
|
||
|
"Invalid register type %d for register %d",
|
||
|
regs[i].type,
|
||
|
regs[i].num
|
||
|
);
|
||
|
#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
|
||
|
}
|
||
|
upsdebugx(3,
|
||
|
"reginit: num:%d, type: %d saddr: %d, xaddr: 0x%x",
|
||
|
regs[i].num,
|
||
|
regs[i].type,
|
||
|
regs[i].saddr,
|
||
|
regs[i].xaddr
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* read registers' memory region */
|
||
|
int read_all_regs(modbus_t *mb, uint16_t *data)
|
||
|
{
|
||
|
int rval;
|
||
|
|
||
|
/* read all HOLDING registers */
|
||
|
rval = modbus_read_registers(mb, regs[H_REG_STARTIDX].xaddr, MAX_H_REGS, data);
|
||
|
if (rval == -1) {
|
||
|
upslogx(LOG_ERR,
|
||
|
"ERROR:(%s) modbus_read: addr:0x%x, length:%8d, path:%s\n",
|
||
|
modbus_strerror(errno),
|
||
|
regs[H_REG_STARTIDX].xaddr,
|
||
|
MAX_H_REGS,
|
||
|
device_path
|
||
|
);
|
||
|
|
||
|
/* on BROKEN PIPE, INVALID CRC and INVALID DATA error try to reconnect */
|
||
|
if (errno == EPIPE || errno == EMBBADDATA || errno == EMBBADCRC) {
|
||
|
upsdebugx(1, "register_read: error(%s)", modbus_strerror(errno));
|
||
|
modbus_reconnect();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* no COIL, INPUT_B or INPUT_R register regions to read */
|
||
|
|
||
|
return rval;
|
||
|
}
|
||
|
|
||
|
/* 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 = 0x00FF;
|
||
|
uint mask16 = 0xFFFF;
|
||
|
|
||
|
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, INVALID CRC and INVALID DATA error try to reconnect */
|
||
|
if (errno == EPIPE || errno == EMBBADDATA || errno == EMBBADCRC) {
|
||
|
upsdebugx(1, "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 = 0x00FF;
|
||
|
uint mask16 = 0xFFFF;
|
||
|
|
||
|
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_write: 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(1, "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;
|
||
|
|
||
|
if (!strcasecmp(cmd, "load.off")) {
|
||
|
data = 1;
|
||
|
rval = register_write(mbctx, regs[FSD].xaddr, regs[FSD].type, &data);
|
||
|
if (rval == -1) {
|
||
|
upslogx(2,
|
||
|
"ERROR:(%s) modbus_write_register: addr:0x%08x, regtype: %d, path:%s\n",
|
||
|
modbus_strerror(errno),
|
||
|
regs[FSD].xaddr,
|
||
|
regs[FSD].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", regs[FSD].xaddr, data);
|
||
|
rval = STAT_INSTCMD_HANDLED;
|
||
|
}
|
||
|
} else {
|
||
|
upslogx(LOG_NOTICE, "instcmd: unknown command [%s] [%s]", cmd, arg);
|
||
|
rval = STAT_INSTCMD_UNKNOWN;
|
||
|
}
|
||
|
return rval;
|
||
|
}
|
||
|
|
||
|
/* read device state, returns 0 on success or -1 on communication error
|
||
|
it formats state depending on register semantics */
|
||
|
int get_dev_state(devreg_t regindx, devstate_t **dvstat)
|
||
|
{
|
||
|
int i; /* local index */
|
||
|
int n;
|
||
|
int rval; /* return value */
|
||
|
static char *ptr = NULL; /* temporary pointer */
|
||
|
uint reg_val; /* register value */
|
||
|
#if READALL_REGS == 0
|
||
|
uint num; /* register number */
|
||
|
regtype_t rtype; /* register type */
|
||
|
int addr; /* register address */
|
||
|
#endif
|
||
|
devstate_t *state; /* device state */
|
||
|
|
||
|
state = *dvstat;
|
||
|
#if READALL_REGS == 1
|
||
|
reg_val = regs_data[regindx];
|
||
|
rval = 0;
|
||
|
#elif READALL_REGS == 0
|
||
|
num = regs[regindx].num;
|
||
|
addr = regs[regindx].xaddr;
|
||
|
rtype = regs[regindx].type;
|
||
|
rval = register_read(mbctx, addr, rtype, ®_val);
|
||
|
if (rval == -1) {
|
||
|
return rval;
|
||
|
}
|
||
|
upsdebugx(3,
|
||
|
"get_dev_state: num: %d, addr: 0x%x, regtype: %d, data: %d",
|
||
|
num,
|
||
|
addr,
|
||
|
rtype,
|
||
|
reg_val
|
||
|
);
|
||
|
#endif
|
||
|
/* process register data */
|
||
|
switch (regindx) {
|
||
|
case CHRG: /* "ups.charge" */
|
||
|
if (reg_val == CHRG_NONE) {
|
||
|
state->charge.state = CHRG_NONE;
|
||
|
state->charge.info = chrgs_i[CHRG_NONE];
|
||
|
} else if (reg_val == CHRG_RECV) {
|
||
|
state->charge.state = CHRG_RECV;
|
||
|
state->charge.info = chrgs_i[CHRG_RECV];
|
||
|
} else if (reg_val == CHRG_BULK) {
|
||
|
state->charge.state = CHRG_BULK;
|
||
|
state->charge.info = chrgs_i[CHRG_BULK];
|
||
|
} else if (reg_val == CHRG_ABSR) {
|
||
|
state->charge.state = CHRG_ABSR;
|
||
|
state->charge.info = chrgs_i[CHRG_ABSR];
|
||
|
} else if (reg_val == CHRG_FLOAT) {
|
||
|
state->charge.state = CHRG_FLOAT;
|
||
|
state->charge.info = chrgs_i[CHRG_FLOAT];
|
||
|
}
|
||
|
upsdebugx(3, "get_dev_state: charge.state: %s", state->charge.info);
|
||
|
break;
|
||
|
case BATV: /* "battery.voltage" */
|
||
|
case LVDC: /* "output.voltage" */
|
||
|
case LCUR: /* "output.current" */
|
||
|
if (reg_val != 0) {
|
||
|
state->reg.val.ui16 = reg_val;
|
||
|
double fval = reg_val / 1000.00; /* convert mV to V, mA to A */
|
||
|
n = snprintf(NULL, 0, "%.2f", fval);
|
||
|
if (ptr != NULL) {
|
||
|
free(ptr);
|
||
|
}
|
||
|
char *fval_s = (char *)xmalloc(sizeof(char) * (n + 1));
|
||
|
ptr = fval_s;
|
||
|
sprintf(fval_s, "%.2f", fval);
|
||
|
state->reg.strval = fval_s;
|
||
|
} else {
|
||
|
state->reg.val.ui16 = 0;
|
||
|
state->reg.strval = "0.00";
|
||
|
}
|
||
|
upsdebugx(3, "get_dev_state: variable: %s", state->reg.strval);
|
||
|
break;
|
||
|
case TBUF:
|
||
|
case BSOH:
|
||
|
case BCEF:
|
||
|
case VAC: /* "input.voltage" */
|
||
|
if (reg_val != 0) {
|
||
|
state->reg.val.ui16 = reg_val;
|
||
|
n = snprintf(NULL, 0, "%d", reg_val);
|
||
|
if (ptr != NULL) {
|
||
|
free(ptr);
|
||
|
}
|
||
|
char *reg_val_s = (char *)xmalloc(sizeof(char) * (n + 1));
|
||
|
ptr = reg_val_s;
|
||
|
sprintf(reg_val_s, "%d", reg_val);
|
||
|
state->reg.strval = reg_val_s;
|
||
|
} else {
|
||
|
state->reg.val.ui16 = 0;
|
||
|
state->reg.strval = "0";
|
||
|
}
|
||
|
upsdebugx(3, "get_dev_state: variable: %s", state->reg.strval);
|
||
|
break;
|
||
|
case BSOC: /* "battery.charge" */
|
||
|
if (reg_val != 0) {
|
||
|
state->reg.val.ui16 = reg_val;
|
||
|
double fval = (double )reg_val * regs[BSOC].scale;
|
||
|
n = snprintf(NULL, 0, "%.2f", fval);
|
||
|
if (ptr != NULL) {
|
||
|
free(ptr);
|
||
|
}
|
||
|
char *fval_s = (char *)xmalloc(sizeof(char) * (n + 1));
|
||
|
ptr = fval_s;
|
||
|
sprintf(fval_s, "%.2f", fval);
|
||
|
state->reg.strval = fval_s;
|
||
|
} else {
|
||
|
state->reg.val.ui16 = 0;
|
||
|
state->reg.strval = "0.00";
|
||
|
}
|
||
|
upsdebugx(3, "get_dev_state: variable: %s", state->reg.strval);
|
||
|
break;
|
||
|
case BTMP: /* "battery.temperature" */
|
||
|
case OTMP: /* "ups.temperature" */
|
||
|
state->reg.val.ui16 = reg_val;
|
||
|
double fval = reg_val - 273.15;
|
||
|
n = snprintf(NULL, 0, "%.2f", fval);
|
||
|
char *fval_s = (char *)xmalloc(sizeof(char) * (n + 1));
|
||
|
if (ptr != NULL) {
|
||
|
free(ptr);
|
||
|
}
|
||
|
ptr = fval_s;
|
||
|
sprintf(fval_s, "%.2f", fval);
|
||
|
state->reg.strval = fval_s;
|
||
|
upsdebugx(3, "get_dev_state: variable: %s", state->reg.strval);
|
||
|
break;
|
||
|
case PMNG: /* "ups.status" & "battery.charge" */
|
||
|
if (reg_val == PMNG_BCKUP) {
|
||
|
state->power.state = PMNG_BCKUP;
|
||
|
state->power.info = pwrmng_i[PMNG_BCKUP];
|
||
|
} else if (reg_val == PMNG_CHRGN) {
|
||
|
state->power.state = PMNG_CHRGN;
|
||
|
state->power.info = pwrmng_i[PMNG_CHRGN];
|
||
|
} else if (reg_val == PMNG_BOOST) {
|
||
|
state->power.state = PMNG_BOOST;
|
||
|
state->power.info = pwrmng_i[PMNG_BOOST];
|
||
|
} else if (reg_val == PMNG_NCHRG) {
|
||
|
state->power.state = PMNG_NCHRG;
|
||
|
state->power.info = pwrmng_i[PMNG_NCHRG];
|
||
|
}
|
||
|
upsdebugx(3, "get_dev_state: power.state: %s", state->reg.strval);
|
||
|
break;
|
||
|
case PRDN: /* "ups.model" */
|
||
|
for (i = 0; i < DEV_NUMOF_MODELS; i++) {
|
||
|
if (prdnm_i[i].val == reg_val) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
state->product.val = reg_val;
|
||
|
state->product.name = prdnm_i[i].name;
|
||
|
upsdebugx(3, "get_dev_state: product.name: %s", state->product.name);
|
||
|
break;
|
||
|
case BSTA:
|
||
|
if (reg_val & BSTA_REVPOL_M) {
|
||
|
bsta->alrm[BSTA_REVPOL_I].actv = 1;
|
||
|
} else {
|
||
|
bsta->alrm[BSTA_REVPOL_I].actv = 0;
|
||
|
}
|
||
|
if (reg_val & BSTA_NOCNND_M) {
|
||
|
bsta->alrm[BSTA_NOCNND_I].actv = 1;
|
||
|
} else {
|
||
|
bsta->alrm[BSTA_NOCNND_I].actv = 0;
|
||
|
}
|
||
|
if (reg_val & BSTA_CLSHCR_M) {
|
||
|
bsta->alrm[BSTA_CLSHCR_I].actv = 1;
|
||
|
} else {
|
||
|
bsta->alrm[BSTA_CLSHCR_I].actv = 0;
|
||
|
}
|
||
|
if (reg_val & BSTA_SULPHD_M) {
|
||
|
bsta->alrm[BSTA_SULPHD_I].actv = 1;
|
||
|
} else {
|
||
|
bsta->alrm[BSTA_SULPHD_I].actv = 0;
|
||
|
}
|
||
|
if (reg_val & BSTA_CHEMNS_M) {
|
||
|
bsta->alrm[BSTA_CHEMNS_I].actv = 1;
|
||
|
} else {
|
||
|
bsta->alrm[BSTA_CHEMNS_I].actv = 0;
|
||
|
}
|
||
|
if (reg_val & BSTA_CNNFLT_M) {
|
||
|
bsta->alrm[BSTA_CNNFLT_I].actv = 1;
|
||
|
} else {
|
||
|
bsta->alrm[BSTA_CNNFLT_I].actv = 0;
|
||
|
}
|
||
|
state->alrm = bsta;
|
||
|
break;
|
||
|
case SCSH:
|
||
|
if (reg_val & SHSC_HIRESI_M) {
|
||
|
shsc->alrm[SHSC_HIRESI_I].actv = 1;
|
||
|
} else {
|
||
|
shsc->alrm[SHSC_HIRESI_I].actv = 0;
|
||
|
}
|
||
|
if (reg_val & SHSC_LOCHEF_M) {
|
||
|
shsc->alrm[SHSC_LOCHEF_I].actv = 1;
|
||
|
} else {
|
||
|
shsc->alrm[SHSC_LOCHEF_I].actv = 0;
|
||
|
}
|
||
|
if (reg_val & SHSC_LOEFCP_M) {
|
||
|
shsc->alrm[SHSC_LOEFCP_I].actv = 1;
|
||
|
} else {
|
||
|
shsc->alrm[SHSC_LOEFCP_I].actv = 0;
|
||
|
}
|
||
|
if (reg_val & SHSC_LOWSOC_M) {
|
||
|
shsc->alrm[SHSC_LOWSOC_I].actv = 1;
|
||
|
} else {
|
||
|
shsc->alrm[SHSC_LOWSOC_I].actv = 0;
|
||
|
}
|
||
|
state->alrm = shsc;
|
||
|
break;
|
||
|
case BVAL:
|
||
|
if (reg_val & BVAL_HIALRM_M) {
|
||
|
bval->alrm[BVAL_HIALRM_I].actv = 1;
|
||
|
} else {
|
||
|
bval->alrm[BVAL_HIALRM_I].actv = 0;
|
||
|
}
|
||
|
if (reg_val & BVAL_LOALRM_M) {
|
||
|
bval->alrm[BVAL_LOALRM_I].actv = 1;
|
||
|
} else {
|
||
|
bval->alrm[BVAL_LOALRM_I].actv = 0;
|
||
|
}
|
||
|
if (reg_val & BVAL_BSTSFL_M) {
|
||
|
bval->alrm[BVAL_BSTSFL_I].actv = 1;
|
||
|
} else {
|
||
|
bval->alrm[BVAL_BSTSFL_I].actv = 0;
|
||
|
}
|
||
|
state->alrm = bval;
|
||
|
break;
|
||
|
case BTSF:
|
||
|
if (reg_val & BTSF_FCND_M) {
|
||
|
btsf->alrm[BTSF_FCND_I].actv = 1;
|
||
|
} else {
|
||
|
btsf->alrm[BTSF_FCND_I].actv = 0;
|
||
|
}
|
||
|
if (reg_val & BTSF_NCND_M) {
|
||
|
btsf->alrm[BTSF_NCND_I].actv = 1;
|
||
|
} else {
|
||
|
btsf->alrm[BTSF_NCND_I].actv = 0;
|
||
|
}
|
||
|
state->alrm = btsf;
|
||
|
break;
|
||
|
case DEVF:
|
||
|
if (reg_val & DEVF_RCALRM_M) {
|
||
|
devf->alrm[DEVF_RCALRM_I].actv = 1;
|
||
|
} else {
|
||
|
devf->alrm[DEVF_RCALRM_I].actv = 0;
|
||
|
}
|
||
|
if (reg_val & DEVF_INALRM_M) {
|
||
|
devf->alrm[DEVF_INALRM_I].actv = 1;
|
||
|
} else {
|
||
|
devf->alrm[DEVF_INALRM_I].actv = 0;
|
||
|
}
|
||
|
if (reg_val & DEVF_LFNAVL_M) {
|
||
|
devf->alrm[DEVF_LFNAVL_I].actv = 1;
|
||
|
} else {
|
||
|
devf->alrm[DEVF_LFNAVL_I].actv = 0;
|
||
|
}
|
||
|
state->alrm = devf;
|
||
|
break;
|
||
|
case VACA:
|
||
|
if (reg_val & VACA_HIALRM_M) {
|
||
|
vaca->alrm[VACA_HIALRM_I].actv = 1;
|
||
|
} else {
|
||
|
vaca->alrm[VACA_HIALRM_I].actv = 0;
|
||
|
}
|
||
|
if (reg_val == VACA_LOALRM_M) {
|
||
|
vaca->alrm[VACA_LOALRM_I].actv = 1;
|
||
|
} else {
|
||
|
vaca->alrm[VACA_LOALRM_I].actv = 0;
|
||
|
}
|
||
|
state->alrm = vaca;
|
||
|
break;
|
||
|
case MAIN:
|
||
|
if (reg_val & MAINS_AVAIL_M) {
|
||
|
mains->alrm[MAINS_AVAIL_I].actv = 1;
|
||
|
} else {
|
||
|
mains->alrm[MAINS_AVAIL_I].actv = 0;
|
||
|
}
|
||
|
if (reg_val == SHUTD_REQST_M) {
|
||
|
mains->alrm[SHUTD_REQST_I].actv = 1;
|
||
|
} else {
|
||
|
mains->alrm[SHUTD_REQST_I].actv = 0;
|
||
|
}
|
||
|
state->alrm = mains;
|
||
|
break;
|
||
|
case OBTA:
|
||
|
if (reg_val == OBTA_HIALRM_V) {
|
||
|
obta->alrm[OBTA_HIALRM_I].actv = 1;
|
||
|
}
|
||
|
state->alrm = obta;
|
||
|
break;
|
||
|
case BINH:
|
||
|
case FSD:
|
||
|
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:
|
||
|
state->reg.val.ui16 = reg_val;
|
||
|
n = snprintf(NULL, 0, "%d", reg_val);
|
||
|
if (ptr != NULL) {
|
||
|
free(ptr);
|
||
|
}
|
||
|
char *reg_val_s = (char *)xmalloc(sizeof(char) * (n + 1));
|
||
|
ptr = reg_val_s;
|
||
|
sprintf(reg_val_s, "%d", reg_val);
|
||
|
state->reg.strval = reg_val_s;
|
||
|
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
|
||
|
}
|
||
|
return rval;
|
||
|
}
|
||
|
|
||
|
/* get driver configuration parameters */
|
||
|
void get_config_vars(void)
|
||
|
{
|
||
|
|
||
|
/* check if serial baud rate is set and 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 and 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 and 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 and 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 and get the value */
|
||
|
if (testvar("dev_slave_id")) {
|
||
|
dev_slave_id = (int)strtol(getval("dev_slave_id"), NULL, 10);
|
||
|
}
|
||
|
upsdebugx(2, "dev_slave_id %d", dev_slave_id);
|
||
|
|
||
|
/* check if response time out (s) is set and 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 and 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 and 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 and 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);
|
||
|
}
|
||
|
|
||
|
/* 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(1, "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, dev_slave_id);
|
||
|
if (rval < 0) {
|
||
|
modbus_free(mbctx);
|
||
|
fatalx(EXIT_FAILURE, "modbus_set_slave: Invalid modbus slave ID %d", dev_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_* */
|
||
|
}
|
||
|
|