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

1227 lines
31 KiB
C

/*
* riello_usb.c: support for Riello USB protocol based UPSes
*
* A document describing the protocol implemented by this driver can be
* found online at:
*
* http://www.networkupstools.org/ups-protocols/riello/PSGPSER-0104.pdf
*
* Copyright (C) 2012 - Elio Parisi <e.parisi@riello-ups.com>
* Copyright (C) 2016 Eaton
*
* 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
*
* Reference of the derivative work: blazer driver
*/
#include "config.h" /* must be the first header */
#include <stdint.h>
#include "main.h"
#include "nut_libusb.h"
#include "usb-common.h"
#include "riello.h"
#define DRIVER_NAME "Riello USB driver"
#define DRIVER_VERSION "0.07"
#define DEFAULT_OFFDELAY 5 /*!< seconds (max 0xFF) */
#define DEFAULT_BOOTDELAY 5 /*!< seconds (max 0xFF) */
/* driver description structure */
upsdrv_info_t upsdrv_info = {
DRIVER_NAME,
DRIVER_VERSION,
"Elio Parisi <e.parisi@riello-ups.com>",
DRV_EXPERIMENTAL,
{ NULL }
};
static uint8_t bufOut[BUFFER_SIZE];
static uint8_t bufIn[BUFFER_SIZE];
static uint8_t gpser_error_control;
static uint8_t input_monophase;
static uint8_t output_monophase;
/*! Time in seconds to delay before shutting down. */
static unsigned int offdelay = DEFAULT_OFFDELAY;
static unsigned int bootdelay = DEFAULT_BOOTDELAY;
static TRielloData DevData;
static usb_communication_subdriver_t *usb = &usb_subdriver;
static usb_dev_handle *udev = NULL;
static USBDevice_t usbdevice;
static USBDeviceMatcher_t *reopen_matcher = NULL;
static USBDeviceMatcher_t *regex_matcher = NULL;
static int (*subdriver_command)(uint8_t *cmd, uint8_t *buf, uint16_t length, uint16_t buflen) = NULL;
static void ussleep(useconds_t usec)
{
if (usec == 1)
usec = 400;
else
usec *= 1000;
usleep(usec);
}
static int cypress_setfeatures()
{
int ret;
bufOut[0] = 0xB0;
bufOut[1] = 0x4;
bufOut[2] = 0x0;
bufOut[3] = 0x0;
bufOut[4] = 0x3;
/* Write features report */
ret = usb_control_msg(udev, USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE,
0x09, /* HID_REPORT_SET = 0x09 */
0 + (0x03 << 8), /* HID_REPORT_TYPE_FEATURE */
0, (usb_ctrl_charbuf) bufOut, 0x5, 1000);
if (ret <= 0) {
upsdebugx(3, "send: %s", ret ? nut_usb_strerror(ret) : "error");
return ret;
}
upsdebugx(3, "send: features report ok");
return ret;
}
static int Send_USB_Packet(uint8_t *send_str, uint16_t numbytes)
{
uint8_t USB_buff_pom[10];
int i, err, size;
/*int errno;*/
/* is input correct ? */
if ((!send_str) || (!numbytes))
return -1;
size = 7;
/* routine which parse report into 4-bytes packet */
for (i=0; i<(numbytes/size); i++) {
USB_buff_pom[0] = 0x37;
USB_buff_pom[1] = send_str[(i*7)];
USB_buff_pom[2] = send_str[(i*7)+1];
USB_buff_pom[3] = send_str[(i*7)+2];
USB_buff_pom[4] = send_str[(i*7)+3];
USB_buff_pom[5] = send_str[(i*7)+4];
USB_buff_pom[6] = send_str[(i*7)+5];
USB_buff_pom[7] = send_str[(i*7)+6];
err = usb_bulk_write(udev, 0x2, (usb_ctrl_charbuf) USB_buff_pom, 8, 1000);
if (err < 0) {
upsdebugx(3, "USB: Send_USB_Packet: send_usb_packet, err = %d %s ", err, strerror(errno));
return err;
}
ussleep(USB_WRITE_DELAY);
}
/* send rest of packet */
if (numbytes % size) {
i = numbytes/size;
memset(USB_buff_pom, '0', sizeof(USB_buff_pom));
USB_buff_pom[0] = 0x30+(numbytes%7);
if ((i*7)<numbytes)
USB_buff_pom[1] = send_str[(i*7)];
if (((i*7)+1)<numbytes)
USB_buff_pom[2] = send_str[(i*7)+1];
if (((i*7)+2)<numbytes)
USB_buff_pom[3] = send_str[(i*7)+2];
if (((i*7)+3)<numbytes)
USB_buff_pom[4] = send_str[(i*7)+3];
if (((i*7)+4)<numbytes)
USB_buff_pom[5] = send_str[(i*7)+4];
if (((i*7)+5)<numbytes)
USB_buff_pom[6] = send_str[(i*7)+5];
if (((i*7)+6)<numbytes)
USB_buff_pom[7] = send_str[(i*7)+6];
err = usb_bulk_write(udev, 0x2, (usb_ctrl_charbuf) USB_buff_pom, 8, 1000);
if (err < 0) {
upsdebugx(3, "USB: Send_USB_Packet: send_usb_packet, err = %d %s ", err, strerror(errno));
return err;
}
ussleep(USB_WRITE_DELAY);
}
return (0);
}
static int Get_USB_Packet(uint8_t *buffer)
{
char inBuf[10];
int err, ep;
size_t size;
/*int errno;*/
/* note: this function stop until some byte(s) is not arrived */
size = 8;
/* Note: depending on libusb API version, size is either int or uint16_t
* either way, likely less than size_t limit. But we don't assign much.
*/
ep = 0x81 | USB_ENDPOINT_IN;
err = usb_bulk_read(udev, ep, (usb_ctrl_charbuf) inBuf, (int)size, 1000);
if (err > 0)
upsdebugx(3, "read: %02X %02X %02X %02X %02X %02X %02X %02X", inBuf[0], inBuf[1], inBuf[2], inBuf[3], inBuf[4], inBuf[5], inBuf[6], inBuf[7]);
if (err < 0){
upsdebugx(3, "USB: Get_USB_Packet: send_usb_packet, err = %d %s ", err, strerror(errno));
return err;
}
/* copy to buffer */
size = (unsigned char)(inBuf[0]) & 0x07;
if (size)
memcpy(buffer, &inBuf[1], size);
if (size > INT_MAX)
return -1;
return (int)size;
}
static int cypress_command(uint8_t *buffer, uint8_t *buf, uint16_t length, uint16_t buflen)
{
int loop = 0;
int ret, i = 0;
uint8_t USB_buff[BUFFER_SIZE];
/* read to flush buffer */
riello_init_serial();
/* send packet */
ret = Send_USB_Packet(buffer, length);
if (ret < 0) {
upsdebugx(3, "Cypress_command send: err %d", ret );
return ret;
}
upsdebugx(3, "send ok");
memset(buf, 0, buflen);
while ((buf_ptr_length < BUFFER_SIZE) && wait_packet) {
memset(USB_buff, 0, sizeof(USB_buff));
ret = Get_USB_Packet(USB_buff);
/*
* Any errors here mean that we are unable to read a reply (which
* will happen after successfully writing a command to the UPS)
*/
if (ret < 0) {
upsdebugx(3, "Cypress_command read: err %d", ret );
return ret;
}
for (i = 0; i < ret; i++ ) {
commbyte = USB_buff[i];
riello_parse_serialport(DEV_RIELLOGPSER, buf, gpser_error_control);
}
loop++;
if (loop>300){
wait_packet=0;
upsdebugx(1, "wait_packet reset");
}
ussleep(10);
}
upsdebugx(3, "in read: %u", buf_ptr_length);
return buf_ptr_length;
}
static void *cypress_subdriver(USBDevice_t *device)
{
NUT_UNUSED_VARIABLE(device);
subdriver_command = &cypress_command;
return NULL;
}
/* Riello (Cypress Semiconductor Corp.) */
#define RIELLO_VENDORID 0x04b4
static usb_device_id_t riello_usb_id[] = {
/* various models */
{ USB_DEVICE(RIELLO_VENDORID, 0x5500), &cypress_subdriver },
/* Terminating entry */
{ 0, 0, NULL }
};
static int device_match_func(USBDevice_t *hd, void *privdata)
{
NUT_UNUSED_VARIABLE(privdata);
if (subdriver_command) {
return 1;
}
switch (is_usb_device_supported(riello_usb_id, hd))
{
case SUPPORTED:
return 1;
case POSSIBLY_SUPPORTED:
case NOT_SUPPORTED:
default:
return 0;
}
}
static USBDeviceMatcher_t device_matcher = {
&device_match_func,
NULL,
NULL
};
/*
* Callback that is called by usb_device_open() that handles USB device
* settings prior to accepting the devide. At the very least claim the
* device here. Detaching the kernel driver will be handled by the
* caller, don't do this here. Return < 0 on error, 0 or higher on
* success.
*/
static int driver_callback(usb_dev_handle *handle, USBDevice_t *device, usb_ctrl_charbuf rdbuf, usb_ctrl_charbufsize rdlen)
{
int ret = 0;
NUT_UNUSED_VARIABLE(device);
NUT_UNUSED_VARIABLE(rdbuf);
NUT_UNUSED_VARIABLE(rdlen);
/*
if ((ret = usb_set_configuration(handle, 1)) < 0) {
upslogx(LOG_WARNING, "Can't set USB configuration: %s", nut_usb_strerror(ret));
return -1;
}
*/
if ((ret = usb_claim_interface(handle, 0)) < 0) {
upslogx(LOG_WARNING, "Can't claim USB interface: %s", nut_usb_strerror(ret));
return -1;
}
/* TODO: HID SET_IDLE to 0 (not necessary?) */
return 1;
}
/*
* Generic command processing function. Send a command and read a reply.
* Returns < 0 on error, 0 on timeout and the number of bytes read on
* success.
*/
static int riello_command(uint8_t *cmd, uint8_t *buf, uint16_t length, uint16_t buflen)
{
int ret;
if (udev == NULL) {
ret = usb->open(&udev, &usbdevice, reopen_matcher, &driver_callback);
upsdebugx (3, "riello_command err udev NULL : %d ", ret);
if (ret < 0)
return ret;
upsdrv_initinfo(); /* reconnect usb cable */
}
ret = (*subdriver_command)(cmd, buf, length, buflen);
if (ret >= 0) {
upsdebugx (3, "riello_command ok: %u", ret);
return ret;
}
upsdebugx (3, "riello_command err: %d", ret);
switch (ret)
{
case ERROR_BUSY: /* Device or resource busy */
fatal_with_errno(EXIT_FAILURE, "Got disconnected by another driver");
#ifndef HAVE___ATTRIBUTE__NORETURN
exit(EXIT_FAILURE); /* Should not get here in practice, but compiler is afraid we can fall through */
#endif
#if WITH_LIBUSB_0_1 /* limit to libusb 0.1 implementation */
case -EPERM: /* Operation not permitted */
fatal_with_errno(EXIT_FAILURE, "Permissions problem");
# ifndef HAVE___ATTRIBUTE__NORETURN
exit(EXIT_FAILURE); /* Should not get here in practice, but compiler is afraid we can fall through */
# endif
#endif
case ERROR_PIPE: /* Broken pipe */
if (usb_clear_halt(udev, 0x81) == 0) {
upsdebugx(1, "Stall condition cleared");
break;
}
#if (defined ETIME) && ETIME && WITH_LIBUSB_0_1
goto fallthrough_case_etime;
case -ETIME: /* Timer expired */
fallthrough_case_etime:
#endif
if (usb_reset(udev) == 0) {
upsdebugx(1, "Device reset handled");
}
goto fallthrough_case_reconnect;
case ERROR_NO_DEVICE: /* No such device */
case ERROR_ACCESS: /* Permission denied */
case ERROR_IO: /* I/O error */
#if WITH_LIBUSB_0_1 /* limit to libusb 0.1 implementation */
case -ENXIO: /* No such device or address */
#endif
case ERROR_NOT_FOUND: /* No such file or directory */
fallthrough_case_reconnect:
/* Uh oh, got to reconnect! */
usb->close(udev);
udev = NULL;
break;
case ERROR_TIMEOUT: /* Connection timed out */
upsdebugx (3, "riello_command err: Resource temporarily unavailable");
break;
case ERROR_OVERFLOW: /* Value too large for defined data type */
#if EPROTO && WITH_LIBUSB_0_1
case -EPROTO: /* Protocol error */
#endif
break;
default:
break;
}
return ret;
}
static int get_ups_nominal()
{
uint8_t length;
int recv;
length = riello_prepare_gn(&bufOut[0], gpser_error_control);
recv = riello_command(&bufOut[0], &bufIn[0], length, LENGTH_GN);
if (recv < 0){
upsdebugx (3, "Get nominal err: read byte: %d", recv);
return recv;
}
if (!wait_packet && foundbadcrc) {
upsdebugx (3, "Get nominal Ko: bad CRC or Checksum");
return -1;
}
/* mandatory */
if (!wait_packet && foundnak) {
upsdebugx (3, "Get nominal Ko: command not supported");
return -1;
}
upsdebugx (3, "Get nominal Ok: read byte: %d", recv);
riello_parse_gn(&bufIn[0], &DevData);
return 0;
}
static int get_ups_status()
{
uint8_t numread, length;
int recv;
length = riello_prepare_rs(&bufOut[0], gpser_error_control);
if (input_monophase)
numread = LENGTH_RS_MM;
else if (output_monophase)
numread = LENGTH_RS_TM;
else
numread = LENGTH_RS_TT;
recv = riello_command(&bufOut[0], &bufIn[0], length, numread);
if (recv < 0){
upsdebugx (3, "Get status err: read byte: %d", recv);
return recv;
}
if (!wait_packet && foundbadcrc) {
upsdebugx (3, "Get status Ko: bad CRC or Checksum");
return -1;
}
/* mandatory */
if (!wait_packet && foundnak) {
upsdebugx (3, "Get status Ko: command not supported");
return -1;
}
upsdebugx (3, "Get status Ok: read byte: %d", recv);
riello_parse_rs(&bufIn[0], &DevData, numread);
return 0;
}
static int get_ups_extended()
{
uint8_t length;
int recv;
length = riello_prepare_re(&bufOut[0], gpser_error_control);
recv = riello_command(&bufOut[0], &bufIn[0], length, LENGTH_RE);
if (recv < 0){
upsdebugx (3, "Get extended err: read byte: %d", recv);
return recv;
}
if (!wait_packet && foundbadcrc) {
upsdebugx (3, "Get extended Ko: bad CRC or Checksum");
return -1;
}
/* optional */
if (!wait_packet && foundnak) {
upsdebugx (3, "Get extended Ko: command not supported");
return 0;
}
upsdebugx (3, "Get extended Ok: read byte: %d", recv);
riello_parse_re(&bufIn[0], &DevData);
return 0;
}
/* Not static, exposed via header. Not used though, currently... */
int get_ups_statuscode()
{
uint8_t length;
int recv;
length = riello_prepare_rc(&bufOut[0], gpser_error_control);
recv = riello_command(&bufOut[0], &bufIn[0], length, LENGTH_RC);
if (recv < 0){
upsdebugx (3, "Get statuscode err: read byte: %d", recv);
return recv;
}
if (!wait_packet && foundbadcrc) {
upsdebugx (3, "Get statuscode Ko: bad CRC or Checksum");
return -1;
}
/* optional */
if (!wait_packet && foundnak) {
upsdebugx (3, "Get statuscode Ko: command not supported");
return 0;
}
upsdebugx (3, "Get statuscode Ok: read byte: %d", recv);
riello_parse_rc(&bufIn[0], &DevData);
return 0;
}
static int riello_instcmd(const char *cmdname, const char *extra)
{
uint8_t length;
int recv;
uint16_t delay;
const char *delay_char;
if (!riello_test_bit(&DevData.StatusCode[0], 1)) {
if (!strcasecmp(cmdname, "load.off")) {
delay = 0;
length = riello_prepare_cs(bufOut, gpser_error_control, delay);
recv = riello_command(&bufOut[0], &bufIn[0], length, LENGTH_DEF);
if (recv < 0) {
upsdebugx (3, "Command load.off err: read byte: %d", recv);
return STAT_INSTCMD_FAILED;
}
if (!wait_packet && foundbadcrc) {
upsdebugx (3, "Command load.off Ko: bad CRC or Checksum");
return STAT_INSTCMD_FAILED;
}
if (!wait_packet && foundnak) {
upsdebugx (3, "Command load.off Ko: command not supported");
return STAT_INSTCMD_FAILED;
}
upsdebugx (3, "Command load.off Ok: read byte: %d", recv);
return STAT_INSTCMD_HANDLED;
}
if (!strcasecmp(cmdname, "load.off.delay")) {
int ipv;
delay_char = dstate_getinfo("ups.delay.shutdown");
ipv = atoi(delay_char);
/* With a "char" in the name, might assume we fit... but :) */
if (ipv < 0 || (intmax_t)ipv > (intmax_t)UINT16_MAX) return STAT_INSTCMD_FAILED;
delay = (uint16_t)ipv;
length = riello_prepare_cs(bufOut, gpser_error_control, delay);
recv = riello_command(&bufOut[0], &bufIn[0], length, LENGTH_DEF);
if (recv < 0) {
upsdebugx (3, "Command load.off.delay err: read byte: %d", recv);
return STAT_INSTCMD_FAILED;
}
if (!wait_packet && foundbadcrc) {
upsdebugx (3, "Command load.off.delay Ko: bad CRC or Checksum");
return STAT_INSTCMD_FAILED;
}
if (!wait_packet && foundnak) {
upsdebugx (3, "Command load.off.delay Ko: command not supported");
return STAT_INSTCMD_FAILED;
}
upsdebugx (3, "Command load.off.delay Ok: read byte: %d", recv);
return STAT_INSTCMD_HANDLED;
}
if (!strcasecmp(cmdname, "load.on")) {
delay = 0;
length = riello_prepare_cr(bufOut, gpser_error_control, delay);
recv = riello_command(&bufOut[0], &bufIn[0], length, LENGTH_DEF);
if (recv < 0) {
upsdebugx (3, "Command load.on err: read byte: %d", recv);
return STAT_INSTCMD_FAILED;
}
if (!wait_packet && foundbadcrc) {
upsdebugx (3, "Command load.on Ko: bad CRC or Checksum");
return STAT_INSTCMD_FAILED;
}
if (!wait_packet && foundnak) {
upsdebugx (3, "Command load.on Ko: command not supported");
return STAT_INSTCMD_FAILED;
}
upsdebugx (3, "Command load.on Ok: read byte: %d", recv);
return STAT_INSTCMD_HANDLED;
}
if (!strcasecmp(cmdname, "load.on.delay")) {
int ipv;
delay_char = dstate_getinfo("ups.delay.reboot");
ipv = atoi(delay_char);
/* With a "char" in the name, might assume we fit... but :) */
if (ipv < 0 || (intmax_t)ipv > (intmax_t)UINT16_MAX) return STAT_INSTCMD_FAILED;
delay = (uint16_t)ipv;
length = riello_prepare_cr(bufOut, gpser_error_control, delay);
recv = riello_command(&bufOut[0], &bufIn[0], length, LENGTH_DEF);
if (recv < 0) {
upsdebugx (3, "Command load.on.delay err: read byte: %d", recv);
return STAT_INSTCMD_FAILED;
}
if (!wait_packet && foundbadcrc) {
upsdebugx (3, "Command load.on.delay Ko: bad CRC or Checksum");
return STAT_INSTCMD_FAILED;
}
if (!wait_packet && foundnak) {
upsdebugx (3, "Command load.on.delay Ko: command not supported");
return STAT_INSTCMD_FAILED;
}
upsdebugx (3, "Command load.on.delay Ok: read byte: %d", recv);
return STAT_INSTCMD_HANDLED;
}
}
else {
if (!strcasecmp(cmdname, "shutdown.return")) {
int ipv;
delay_char = dstate_getinfo("ups.delay.shutdown");
ipv = atoi(delay_char);
/* With a "char" in the name, might assume we fit... but :) */
if (ipv < 0 || (intmax_t)ipv > (intmax_t)UINT16_MAX) return STAT_INSTCMD_FAILED;
delay = (uint16_t)ipv;
length = riello_prepare_cs(bufOut, gpser_error_control, delay);
recv = riello_command(&bufOut[0], &bufIn[0], length, LENGTH_DEF);
if (recv < 0) {
upsdebugx (3, "Command shutdown.return err: read byte: %d", recv);
return STAT_INSTCMD_FAILED;
}
if (!wait_packet && foundbadcrc) {
upsdebugx (3, "Command shutdown.return Ko: bad CRC or Checksum");
return STAT_INSTCMD_FAILED;
}
if (!wait_packet && foundnak) {
upsdebugx (3, "Command shutdown.return Ko: command not supported");
return STAT_INSTCMD_FAILED;
}
upsdebugx (3, "Command shutdown.return Ok: read byte: %d", recv);
return STAT_INSTCMD_HANDLED;
}
}
if (!strcasecmp(cmdname, "shutdown.stop")) {
length = riello_prepare_cd(bufOut, gpser_error_control);
recv = riello_command(&bufOut[0], &bufIn[0], length, LENGTH_DEF);
if (recv < 0) {
upsdebugx (3, "Command shutdown.stop err: read byte: %d", recv);
return STAT_INSTCMD_FAILED;
}
if (!wait_packet && foundbadcrc) {
upsdebugx (3, "Command shutdown.stop Ko: bad CRC or Checksum");
return STAT_INSTCMD_FAILED;
}
if (!wait_packet && foundnak) {
upsdebugx (3, "Command shutdown.stop Ko: command not supported");
return STAT_INSTCMD_FAILED;
}
upsdebugx (3, "Command shutdown.stop Ok: read byte: %d", recv);
return STAT_INSTCMD_HANDLED;
}
if (!strcasecmp(cmdname, "test.panel.start")) {
length = riello_prepare_tp(bufOut, gpser_error_control);
recv = riello_command(&bufOut[0], &bufIn[0], length, LENGTH_DEF);
if (recv < 0) {
upsdebugx (3, "Command test.panel.start err: read byte: %d", recv);
return STAT_INSTCMD_FAILED;
}
if (!wait_packet && foundbadcrc) {
upsdebugx (3, "Command test.panel.start Ko: bad CRC or Checksum");
return STAT_INSTCMD_FAILED;
}
if (!wait_packet && foundnak) {
upsdebugx (3, "Command test.panel.start Ko: command not supported");
return STAT_INSTCMD_FAILED;
}
upsdebugx (3, "Command test.panel.start Ok: read byte: %d", recv);
return STAT_INSTCMD_HANDLED;
}
if (!strcasecmp(cmdname, "test.battery.start")) {
length = riello_prepare_tb(bufOut, gpser_error_control);
recv = riello_command(&bufOut[0], &bufIn[0], length, LENGTH_DEF);
if (recv < 0) {
upsdebugx (3, "Command test.battery.start err: read byte: %d", recv);
return STAT_INSTCMD_FAILED;
}
if (!wait_packet && foundbadcrc) {
upsdebugx (3, "Command test.battery.start Ko: bad CRC or Checksum");
return STAT_INSTCMD_FAILED;
}
if (!wait_packet && foundnak) {
upsdebugx (3, "Command test.battery.start Ko: command not supported");
return STAT_INSTCMD_FAILED;
}
upsdebugx (3, "Command test.battery.start Ok: read byte: %d", recv);
return STAT_INSTCMD_HANDLED;
}
upslogx(LOG_NOTICE, "instcmd: unknown command [%s] [%s]", cmdname, extra);
return STAT_INSTCMD_UNKNOWN;
}
static int start_ups_comm()
{
uint16_t length;
int recv;
upsdebugx (2, "entering start_ups_comm()\n");
cypress_setfeatures();
length = riello_prepare_gi(&bufOut[0]);
recv = riello_command(&bufOut[0], &bufIn[0], length, LENGTH_GI);
if (recv < 0) {
upsdebugx (3, "Get identif err: read byte: %d", recv);
return recv;
}
if (!wait_packet && foundbadcrc) {
upsdebugx (3, "Get identif Ko: bad CRC or Checksum");
return 1;
}
if (!wait_packet && foundnak) {
upsdebugx (3, "Get identif Ko: command not supported");
return 1;
}
upsdebugx (3, "Get identif Ok: read byte: %u", recv);
return 0;
}
void upsdrv_help(void)
{
}
void upsdrv_makevartable(void)
{
}
void upsdrv_initups(void)
{
const struct {
const char *name;
int (*command)(uint8_t *cmd, uint8_t *buf, uint16_t length, uint16_t buflen);
} subdriver[] = {
{ "cypress", &cypress_command },
{ NULL, NULL }
};
int ret;
char *regex_array[7];
char *subdrv = getval("subdriver");
warn_if_bad_usb_port_filename(device_path);
regex_array[0] = getval("vendorid");
regex_array[1] = getval("productid");
regex_array[2] = getval("vendor");
regex_array[3] = getval("product");
regex_array[4] = getval("serial");
regex_array[5] = getval("bus");
regex_array[6] = getval("device");
/* pick up the subdriver name if set explicitly */
if (subdrv) {
int i;
if (!regex_array[0] || !regex_array[1]) {
fatalx(EXIT_FAILURE, "When specifying a subdriver, 'vendorid' and 'productid' are mandatory.");
}
for (i = 0; subdriver[i].name; i++) {
if (strcasecmp(subdrv, subdriver[i].name)) {
continue;
}
subdriver_command = subdriver[i].command;
break;
}
if (!subdriver_command) {
fatalx(EXIT_FAILURE, "Subdriver \"%s\" not found!", subdrv);
}
}
ret = USBNewRegexMatcher(&regex_matcher, regex_array, REG_ICASE | REG_EXTENDED);
switch (ret)
{
case -1:
fatal_with_errno(EXIT_FAILURE, "USBNewRegexMatcher");
case 0:
break; /* all is well */
default:
fatalx(EXIT_FAILURE, "invalid regular expression: %s", regex_array[ret]);
}
/* link the matchers */
regex_matcher->next = &device_matcher;
ret = usb->open(&udev, &usbdevice, regex_matcher, &driver_callback);
if (ret < 0) {
fatalx(EXIT_FAILURE,
"No supported devices found. Please check your device availability with 'lsusb'\n"
"and make sure you have an up-to-date version of NUT. If this does not help,\n"
"try running the driver with at least 'subdriver', 'vendorid' and 'productid'\n"
"options specified. Please refer to the man page for details about these options\n"
"(man 8 riello_usb).\n");
}
if (!subdriver_command) {
fatalx(EXIT_FAILURE, "No subdriver selected");
}
/* create a new matcher for later reopening */
ret = USBNewExactMatcher(&reopen_matcher, &usbdevice);
if (ret) {
fatal_with_errno(EXIT_FAILURE, "USBNewExactMatcher");
}
/* link the matchers */
reopen_matcher->next = regex_matcher;
dstate_setinfo("ups.vendorid", "%04x", usbdevice.VendorID);
dstate_setinfo("ups.productid", "%04x", usbdevice.ProductID);
}
void upsdrv_initinfo(void)
{
int ret;
ret = start_ups_comm();
if (ret < 0)
fatalx(EXIT_FAILURE, "No communication with UPS");
else if (ret > 0)
fatalx(EXIT_FAILURE, "Bad checksum or NACK");
else
upsdebugx(2, "Communication with UPS established");
riello_parse_gi(&bufIn[0], &DevData);
gpser_error_control = DevData.Identif_bytes[4]-0x30;
if ((DevData.Identif_bytes[0] == '1') || (DevData.Identif_bytes[0] == '2'))
input_monophase = 1;
else {
input_monophase = 0;
dstate_setinfo("input.phases", "%u", 3);
dstate_setinfo("input.phases", "%u", 3);
dstate_setinfo("input.bypass.phases", "%u", 3);
}
if ((DevData.Identif_bytes[0] == '1') || (DevData.Identif_bytes[0] == '3'))
output_monophase = 1;
else {
output_monophase = 0;
dstate_setinfo("output.phases", "%u", 3);
}
dstate_setinfo("device.mfr", "RPS S.p.a.");
dstate_setinfo("device.model", "%s", (unsigned char*) DevData.ModelStr);
dstate_setinfo("device.serial", "%s", (unsigned char*) DevData.Identification);
dstate_setinfo("device.type", "ups");
dstate_setinfo("ups.mfr", "RPS S.p.a.");
dstate_setinfo("ups.model", "%s", (unsigned char*) DevData.ModelStr);
dstate_setinfo("ups.serial", "%s", (unsigned char*) DevData.Identification);
dstate_setinfo("ups.firmware", "%s", (unsigned char*) DevData.Version);
if (get_ups_nominal() == 0) {
dstate_setinfo("ups.realpower.nominal", "%u", DevData.NomPowerKW);
dstate_setinfo("ups.power.nominal", "%u", DevData.NomPowerKVA);
dstate_setinfo("output.voltage.nominal", "%u", DevData.NominalUout);
dstate_setinfo("output.frequency.nominal", "%.1f", DevData.NomFout/10.0);
dstate_setinfo("battery.voltage.nominal", "%u", DevData.NomUbat);
dstate_setinfo("battery.capacity", "%u", DevData.NomBatCap);
}
/* commands ----------------------------------------------- */
dstate_addcmd("load.off");
dstate_addcmd("load.on");
dstate_addcmd("load.off.delay");
dstate_addcmd("load.on.delay");
dstate_addcmd("shutdown.return");
dstate_addcmd("shutdown.stop");
dstate_addcmd("test.battery.start");
dstate_addcmd("test.panel.start");
dstate_setinfo("ups.delay.shutdown", "%u", offdelay);
dstate_setflags("ups.delay.shutdown", ST_FLAG_RW | ST_FLAG_STRING);
dstate_setaux("ups.delay.shutdown", 3);
dstate_setinfo("ups.delay.reboot", "%u", bootdelay);
dstate_setflags("ups.delay.reboot", ST_FLAG_RW | ST_FLAG_STRING);
dstate_setaux("ups.delay.reboot", 3);
/* install handlers */
/* upsh.setvar = hid_set_value; setvar; */
/* note: for a transition period, these data are redundant! */
/* dstate_setinfo("device.mfr", "skel manufacturer"); */
/* dstate_setinfo("device.model", "longrun 15000"); */
upsh.instcmd = riello_instcmd;
}
void upsdrv_shutdown(void)
__attribute__((noreturn));
void upsdrv_shutdown(void)
{
/* tell the UPS to shut down, then return - DO NOT SLEEP HERE */
int retry;
/* maybe try to detect the UPS here, but try a shutdown even if
it doesn't respond at first if possible */
/* replace with a proper shutdown function */
/* you may have to check the line status since the commands
for toggling power are frequently different for OL vs. OB */
/* OL: this must power cycle the load if possible */
/* OB: the load must remain off until the power returns */
for (retry = 1; retry <= MAXTRIES; retry++) {
if (riello_instcmd("shutdown.stop", NULL) != STAT_INSTCMD_HANDLED) {
continue;
}
if (riello_instcmd("shutdown.return", NULL) != STAT_INSTCMD_HANDLED) {
continue;
}
fatalx(EXIT_SUCCESS, "Shutting down");
}
fatalx(EXIT_FAILURE, "Shutdown failed!");
}
void upsdrv_updateinfo(void)
{
uint8_t getextendedOK;
static int countlost = 0;
int stat;
upsdebugx(1, "countlost %d",countlost);
if (countlost > 0){
upsdebugx(1, "Communication with UPS is lost: status read failed!");
if (countlost == COUNTLOST) {
dstate_datastale();
upslogx(LOG_WARNING, "Communication with UPS is lost: status read failed!");
}
}
stat = get_ups_status();
upsdebugx(1, "get_ups_status() %d",stat );
if (stat < 0) {
if (countlost < COUNTLOST)
countlost++;
return;
}
if (get_ups_extended() == 0)
getextendedOK = 1;
else
getextendedOK = 0;
if (countlost == COUNTLOST)
upslogx(LOG_NOTICE, "Communication with UPS is re-established!");
dstate_setinfo("input.frequency", "%.2f", DevData.Finp/10.0);
dstate_setinfo("input.bypass.frequency", "%.2f", DevData.Fbypass/10.0);
dstate_setinfo("output.frequency", "%.2f", DevData.Fout/10.0);
dstate_setinfo("battery.voltage", "%.1f", DevData.Ubat/10.0);
if ((DevData.BatCap < 0xFFFF) && (DevData.BatTime < 0xFFFF)) {
dstate_setinfo("battery.charge", "%u", DevData.BatCap);
dstate_setinfo("battery.runtime", "%u", DevData.BatTime*60);
}
if (DevData.Tsystem < 0xFF)
dstate_setinfo("ups.temperature", "%u", DevData.Tsystem);
if (input_monophase) {
dstate_setinfo("input.voltage", "%u", DevData.Uinp1);
dstate_setinfo("input.bypass.voltage", "%u", DevData.Ubypass1);
}
else {
dstate_setinfo("input.L1-N.voltage", "%u", DevData.Uinp1);
dstate_setinfo("input.L2-N.voltage", "%u", DevData.Uinp2);
dstate_setinfo("input.L3-N.voltage", "%u", DevData.Uinp3);
dstate_setinfo("input.bypass.L1-N.voltage", "%u", DevData.Ubypass1);
dstate_setinfo("input.bypass.L2-N.voltage", "%u", DevData.Ubypass2);
dstate_setinfo("input.bypass.L3-N.voltage", "%u", DevData.Ubypass3);
}
if (output_monophase) {
dstate_setinfo("output.voltage", "%u", DevData.Uout1);
dstate_setinfo("output.power.percent", "%u", DevData.Pout1);
dstate_setinfo("ups.load", "%u", DevData.Pout1);
}
else {
dstate_setinfo("output.L1-N.voltage", "%u", DevData.Uout1);
dstate_setinfo("output.L2-N.voltage", "%u", DevData.Uout2);
dstate_setinfo("output.L3-N.voltage", "%u", DevData.Uout3);
dstate_setinfo("output.L1.power.percent", "%u", DevData.Pout1);
dstate_setinfo("output.L2.power.percent", "%u", DevData.Pout2);
dstate_setinfo("output.L3.power.percent", "%u", DevData.Pout3);
dstate_setinfo("ups.load", "%u", (DevData.Pout1+DevData.Pout2+DevData.Pout3)/3);
}
status_init();
/* AC Fail */
if (riello_test_bit(&DevData.StatusCode[0], 1))
status_set("OB");
else
status_set("OL");
/* LowBatt */
if ((riello_test_bit(&DevData.StatusCode[0], 1)) &&
(riello_test_bit(&DevData.StatusCode[0], 0)))
status_set("LB");
/* Standby */
if (!riello_test_bit(&DevData.StatusCode[0], 3))
status_set("OFF");
/* On Bypass */
if (riello_test_bit(&DevData.StatusCode[1], 3))
status_set("BYPASS");
/* Overload */
if (riello_test_bit(&DevData.StatusCode[4], 2))
status_set("OVER");
/* Buck */
if (riello_test_bit(&DevData.StatusCode[1], 0))
status_set("TRIM");
/* Boost */
if (riello_test_bit(&DevData.StatusCode[1], 1))
status_set("BOOST");
/* Replace battery */
if (riello_test_bit(&DevData.StatusCode[2], 0))
status_set("RB");
/* Charging battery */
if (riello_test_bit(&DevData.StatusCode[2], 2))
status_set("CHRG");
status_commit();
dstate_dataok();
if (getextendedOK) {
dstate_setinfo("output.L1.power", "%u", DevData.Pout1VA);
dstate_setinfo("output.L2.power", "%u", DevData.Pout2VA);
dstate_setinfo("output.L3.power", "%u", DevData.Pout3VA);
dstate_setinfo("output.L1.realpower", "%u", DevData.Pout1W);
dstate_setinfo("output.L2.realpower", "%u", DevData.Pout2W);
dstate_setinfo("output.L3.realpower", "%u", DevData.Pout3W);
dstate_setinfo("output.L1.current", "%u", DevData.Iout1);
dstate_setinfo("output.L2.current", "%u", DevData.Iout2);
dstate_setinfo("output.L3.current", "%u", DevData.Iout3);
}
poll_interval = 2;
countlost = 0;
/* if (get_ups_statuscode() != 0)
upsdebugx(2, "Communication is lost");
else {
}*/
/*
* ret = ser_get_line(upsfd, temp, sizeof(temp), ENDCHAR, IGNCHARS);
*
* if (ret < STATUS_LEN) {
* upslogx(LOG_ERR, "Short read from UPS");
* dstate_datastale();
* return;
* }
*/
/* dstate_setinfo("var.name", ""); */
/* if (ioctl(upsfd, TIOCMGET, &flags)) {
* upslog_with_errno(LOG_ERR, "TIOCMGET");
* dstate_datastale();
* return;
* }
*/
/* status_init();
*
* if (ol)
* status_set("OL");
* else
* status_set("OB");
* ...
*
* status_commit();
*
* dstate_dataok();
*/
/*
* poll_interval = 2;
*/
}
void upsdrv_cleanup(void)
{
usb->close(udev);
USBFreeExactMatcher(reopen_matcher);
USBFreeRegexMatcher(regex_matcher);
free(usbdevice.Vendor);
free(usbdevice.Product);
free(usbdevice.Serial);
free(usbdevice.Bus);
free(usbdevice.Device);
}