[Nut-upsdev] fixing the cpsups driver

Doug Reynolds mav at wastegate.net
Sat Dec 30 21:22:38 CET 2006


Arjen de Korte wrote:
> Good! Finally it's getting the attention that it badly needs.
>   
Yes..  I was disappointed on the way the driver worked when I bought the 
unit I have now.  It was a relatively inexpensive unit, and has a good 
capacity.

> 1) Get developer access. You need to subscribe to Alioth first
>
> 	https://alioth.debian.org/account/register.php
>
> and send the login name you created to Arnaud Quette
> <aquette.dev at gmail.com> who is managing the project.
>
> 2) Since Arnaud is not around at the moment (on holidays), you can also
> post the modified sources here. I (or someone else) can then pick them up
> and submit them to the trunk. This driver is still marked 'experimental'
> so unless the changes are radical, anything goes.
>
> Make sure you have read docs/developers.txt to learn about the coding
> standards, before submitting.
>
> Best regards, Arjen
>   

ok, I will do that.  I will attach my current cpsups.c to this message..

/* cpsups.c - model specific routines for CyberPower text protocol UPSes

   Copyright (C) 2003  Walt Holman <waltabbyh at comcast.net>
   with thanks to Russell Kroll <rkroll at exploits.org>

   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
*/

/* This driver started out as the bestups.c driver from 1.5.11 -
 * I've hacked it up every which way to get it to function
 * with a CPS1100AVR. Thanks go to the guys at
 * http://networkupstools.org for creating a very nice toolset.
*/

#include "cpsups.h"

#define DRV_VERSION ".04"

static void model_set(const char *abbr, const char *rating)
{

        /*
         * Added: Brad Sawatzky <brad+nut at lamorak.phys.virginia.edu> 02Jun04
         * NOTE: I have no idea how to set the runtime parameter... I 
basically
         * scaled up linearly from the 1100 and 500 entries based on the
         * 'voltage'.  The realtime runtime calculated under load looks
         * reasonable.
         */
    if (!strcmp(abbr, "#1500VA    ")) {
            dstate_setinfo("ups.mfr", "%s", "CyberPower");
            dstate_setinfo("ups.model", "CPS1500AVR %s", rating);
        dstate_setinfo("ups.runtime", "%s", "90");
        dstate_setinfo("ups.power.nominal", "%s", "1500");
            return;
    }

    /* Added: Doug Reynolds 29Dec06 */
        if (!strcmp(abbr, "#BC1200    ")) {
        dstate_setinfo("ups.mfr", "%s", "CyberPower");
        dstate_setinfo("ups.model", "CPS1200VA %s", rating);
        dstate_setinfo("ups.runtime", "%s", "60");
        dstate_setinfo("ups.power.nominal", "%s", "1200");
        return;
    }
   
    if (!strcmp(abbr, "#1100VA    ")) {
            dstate_setinfo("ups.mfr", "%s", "CyberPower");
            dstate_setinfo("ups.model", "CPS1100VA %s", rating);
        dstate_setinfo("ups.runtime", "%s", "60");
        dstate_setinfo("ups.power.nominal", "%s", "1100");
            return;
    }

    /* Added: Armin Diehl <diehl at ...> 14Dec04 */
    if (!strcmp(abbr, "#1000VA    ")) {
        dstate_setinfo("ups.mfr", "%s", "MicroDowell");
        dstate_setinfo("ups.model", "B.Box BP 1000 %s", rating);
        dstate_setinfo("ups.runtime", "%s", "50");
        dstate_setinfo("ups.voltage", "%s", "1000");
        return;
    }
           
    if (!strcmp(abbr, "#825VA     ")) {
        dstate_setinfo("ups.mfr", "%s", "CyberPower");
        dstate_setinfo("ups.model", "CPS825VA %s", rating);
        dstate_setinfo("ups.runtime", "%s", "29");
        dstate_setinfo("ups.power.nominal", "%s", "825");
        return;
    }

    /* Added: Armin Diehl <diehl at ...> 14Dec04 */
    if (!strcmp(abbr, "#750VA     ")) {
        dstate_setinfo("ups.mfr", "%s", "MicroDowell");
        dstate_setinfo("ups.model", "B.Box BP 750 %s", rating);
        dstate_setinfo("ups.runtime", "%s", "29");
        dstate_setinfo("ups.voltage", "%s", "825");
        return;
    }

    if (!strcmp(abbr, "#500VA     ")) {
                dstate_setinfo("ups.mfr", "%s", "CyberPower");
                dstate_setinfo("ups.model", "OP500TE %s", rating);
                dstate_setinfo("ups.runtime", "%s", "16.5");
                dstate_setinfo("ups.power.nominal", "%s", "500");
        return;
    }

    dstate_setinfo("ups.mfr", "%s", "Unknown");
    dstate_setinfo("ups.model", "%s (%s)", abbr, rating);
    dstate_setinfo("ups.runtime", "%s", "1");
    dstate_setinfo("ups.power.nominal", "%s", "1");

    printf("Unknown model detected - please report this ID: '%s'\n", abbr);
}

static void clr_cps_serial(void)
{
    int dtr_bit = TIOCM_DTR;

    ioctl(upsfd, TIOCMBIC, &dtr_bit);
}

static void set_cps_serial(void)
{
        int dtr_bit = TIOCM_DTR;

        ioctl(upsfd, TIOCMBIS, &dtr_bit);
        tcflush(upsfd, TCIOFLUSH);
}

static int instcmd(const char *cmdname, const char *extra)
{
    /* The following commands also appear to be valid on the CPS1100 */

    if (!strcasecmp(cmdname, "test.battery.stop")) {
        ser_send_pace(upsfd, UPSDELAY, "CT\r");
        return STAT_INSTCMD_HANDLED;
    }

    if (!strcasecmp(cmdname, "test.battery.start")) {
        ser_send_pace(upsfd, UPSDELAY, "T\r");
        return STAT_INSTCMD_HANDLED;
    }

    upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
    return STAT_INSTCMD_UNKNOWN;
}


static int get_ident(char *buf, size_t bufsize)
{
    int    a, rdy, ret;
    char    dr[256];
   
/*
 *              The cyberpower monitoring program sends the first \r 
seperate
 *              from the identity request.  It sends a \r, waits for a #2,
 *              then sends a P4\r
 *
 *              Hopefully, I will be able to duplicate that function here.
 *        This seems to be a better way for the driver to accurately
 *        detect unknown models.  In former state, it would fail to
 *        detect anything.
 *
 */
   
    for (a = 0; a < MAXTRIES; a++) {
       
        set_cps_serial();

        ser_send_pace(upsfd, UPSDELAY, "\r");
        
        ret = ser_get_line(upsfd, dr, sizeof(dr), ENDCHAR, "",
            SER_WAIT_SEC, SER_WAIT_USEC);

        clr_cps_serial();

        if ((dr[0] == '#') && (dr[1] == '2')) {
            upsdebugx(2, "get_ident: got [%s], ready to poll for ups 
model", dr);
            rdy = 1;
            break;
            }
        else {
            upsdebugx(2, "get_ident: got [%s], instead of '#2', 
retrying", dr);
            rdy = 0;
            }
        }


    if (rdy) {
        for (a = 0; a < MAXTRIES; a++) {

            set_cps_serial();
            ser_send_pace(upsfd, UPSDELAY, "P4\r");
   
            ret = ser_get_line(upsfd, buf, bufsize, ENDCHAR, "",
                SER_WAIT_SEC, SER_WAIT_USEC);
               
            clr_cps_serial();

            set_cps_serial();
            ser_send_pace(upsfd, UPSDELAY, "P4\r");

            ret = ser_get_line(upsfd, buf, bufsize, ENDCHAR, "",
                SER_WAIT_SEC, SER_WAIT_USEC);

            clr_cps_serial();

            if (ret > 0)
                upsdebugx(2, "get_ident: got [%s]", buf);

            /* buf must start with # and be in the range [25-27] */

            if ((ret > 0) && (buf[0] == '#') && (strlen(buf) >= 25) && 
(strlen(buf) <= 50)) {
                rdy = 1;
                break;
                }
            else {
                rdy = 0;
                upsdebugx(2, "get_ident: got [%s], instead of '#2', 
retrying", buf);
                }
            }
        }
   
    if (rdy) {
        for (a = 0; a < MAXTRIES; a++) {

            set_cps_serial();
            ser_send_pace(upsfd, UPSDELAY, "P3\r");

            ret = ser_get_line(upsfd, dr, sizeof(dr), ENDCHAR, "",
                SER_WAIT_SEC, SER_WAIT_USEC);

            clr_cps_serial();

            if ((ret > 0) && (dr[0] == '#') && (strlen(dr) > 2)) {

                rdy = 1;
                upsdebugx(2, "get_ident: P3 smart mode ok");
                break;
                }
            else {
                rdy = 0;
                upsdebugx(2, "get_ident: got [%s], not smart mode ok", dr);
                }
            }
        }

    if (rdy) {
        for (a = 0; a < MAXTRIES; a++) {
       
        set_cps_serial();
        ser_send_pace(upsfd, UPSDELAY, "P2\r");

        ret = ser_get_line(upsfd, dr, sizeof(dr), ENDCHAR, "",
            SER_WAIT_SEC, SER_WAIT_USEC);

        clr_cps_serial();

        if ((ret > 0) && (dr[0] == '#') && (strlen(dr) > 2)) {

            rdy = 1;
            upsdebugx(2, "get_ident: P2 smart mode ok");
            break;
            }
        else {
            rdy = 0;
            upsdebugx(2, "get_ident: got [%s], not smart mode ok", dr);
            }
        }   
    }

    if (rdy) {
        for (a = 0; a < MAXTRIES; a++) {


        set_cps_serial();
        ser_send_pace(upsfd, UPSDELAY, "P1\r");

        ret = ser_get_line(upsfd, dr, sizeof(dr), ENDCHAR, "",
            SER_WAIT_SEC, SER_WAIT_USEC);

        clr_cps_serial();

        if ((ret > 0) && (dr[0] == '#') && (strlen(dr) > 2)) {

            rdy = 1;
            upsdebugx(2, "get_ident: P1 smart mode ok");
            break;
            }
        else {
            rdy = 0;
            upsdebugx(2, "get_ident: got [%s], not smart mode ok", 
dr);                       
            }
        }
    }

    if (rdy) {
        for (a = 0; a < MAXTRIES; a++) {
       
        set_cps_serial();
        ser_send_pace(upsfd, UPSDELAY, "P7\r");

        ret = ser_get_line(upsfd, dr, sizeof(dr), ENDCHAR, "",
            SER_WAIT_SEC, SER_WAIT_USEC);

        clr_cps_serial();

        if ((ret > 0) && (dr[0] == '#') && (strlen(dr) > 2)) {

            rdy = 1;
            upsdebugx(2, "get_ident: P7 smart mode ok");
            break;
            }
        else {
            rdy = 0;
            upsdebugx(2, "get_ident: got [%s], not smart mode ok", dr);
            }
        }
    }

    if (rdy) {
        for (a = 0; a < MAXTRIES; a++) {

        set_cps_serial();
        ser_send_pace(upsfd, UPSDELAY, "P6\r");

        ret = ser_get_line(upsfd, dr, sizeof(dr), ENDCHAR, "",
            SER_WAIT_SEC, SER_WAIT_USEC);

        clr_cps_serial();

        if ((ret > 0) && (dr[0] == '#') && (strlen(dr) > 2)) {

            rdy = 1;
            upsdebugx(2, "get_ident: P6 smart mode ok");
            break;
            }
        else {
            rdy = 0;
            upsdebugx(2, "get_ident: got [%s], not smart mode ok", dr);
            }
        }
    }

    if (rdy) {
        for (a = 0; a < MAXTRIES; a++) {
       
        set_cps_serial();
        ser_send_pace(upsfd, UPSDELAY, "P8\r");

        ret = ser_get_line(upsfd, dr, sizeof(dr), ENDCHAR, "",
            SER_WAIT_SEC, SER_WAIT_USEC);

        clr_cps_serial();

        if ((ret > 0) && (dr[0] == '#') && (strlen(dr) > 2)) {

            rdy = 1;
            upsdebugx(2, "get_ident: P8 smart mode ok");
            break;
            }
        else {
            rdy = 0;
            upsdebugx(2, "get_ident: got [%s], not smart mode ok", dr);
            }
        }
    }


    if (rdy) {
        for (a = 0; a < MAXTRIES; a++) {
           
            set_cps_serial();
            ser_send_pace(upsfd, UPSDELAY, "P9\r");
           
            ret = ser_get_line(upsfd, dr, sizeof(dr), ENDCHAR, "",
                SER_WAIT_SEC, SER_WAIT_USEC);

            clr_cps_serial();
           
            if ((ret > 0) && (dr[0] == '#') && (strlen(dr) > 2) && 
(strlen(dr) < 8)) {
               
                rdy = 1;
                upsdebugx(2, "get_ident: P9 smart mode ok");
                break;
                }
            else {
                rdy = 0;
                upsdebugx(2, "get_ident: got [%s], not smart mode ok", dr);
                }

            }
        }   

   
   
    if (!rdy) {
        upslogx(LOG_INFO, "Giving up on hardware detection after %d 
tries", MAXTRIES);
        return 0;
        }
    else    {
        upsdebugx(2, "get_ident: ups initialized and ready");
        return 1;
        }
}

static int scan_poll_values(char *buf)
{
    char values[20][200], *pos;
    int i = 0, battremain, length, rseconds;
    double rminutes;

/*    These are used to hold status of UPS.
 *    tmp1 = online/onbattery status
 */
    char *tmp1 = &buf[pollstatusmap[POLL_UPSSTATUS].begin];

    while ((pollstatusmap[i].end != 0))
    {
        pos = &buf[pollstatusmap[i].begin];
        length = pollstatusmap[i].end + 1 - pollstatusmap[i].begin;
        strncpy(values[i],pos,length);
        i++;
    }

    if ((*tmp1 & CPS_STAT_OL) && !(*tmp1 & CPS_STAT_OB))
        status_set("OL");

    if (*tmp1 & CPS_STAT_OB)
        status_set("OB");

    if (*tmp1 & CPS_STAT_CAL)
        status_set("CAL");

    if (*tmp1 & CPS_STAT_LB)
        status_set("LB");

    if (*tmp1 == 0)
        status_set("OFF");


    pos = values[3];
        battremain = strtol(dstate_getinfo("ups.power.nominal"),NULL,10) 
* (strtod(pos,NULL)/100);

    /* Figure out runtime minutes */
    rminutes = strtod(dstate_getinfo("ups.runtime"),NULL) *
        ((battremain * ( 1 - (strtod (values[2],NULL) / 100 ))) /
         strtol(dstate_getinfo("ups.power.nominal"),NULL,10));
    rseconds = ((int)(rminutes*100) - ((int)rminutes)*100) * 0.6 ;

        dstate_setinfo("input.voltage", "%g", strtod(values[0],NULL));
        dstate_setinfo("output.voltage", "%g", strtod(values[1],NULL));
        dstate_setinfo("ups.load", "%li", strtol(values[2],NULL,10));
        dstate_setinfo("input.frequency", "%g", strtod(values[5],NULL));
        dstate_setinfo("ups.temperature", "%li", strtol(values[4],NULL,10));
    dstate_setinfo("battery.charge", "%02.1f", strtod(values[3],NULL));
    dstate_setinfo("battery.runtime", "%2.0f:%02d", rminutes, rseconds);

    status_commit();
    dstate_dataok();
    return 0;
}

static void ups_ident(void)
{
    char    buf[256], *ptr, *com, *model, *rating;
    int    i;

    if (!get_ident(buf, sizeof(buf)))
        fatalx("Unable to detect a CyberPower text protocol UPS");

    model = rating = NULL;

    ptr = buf;

    /* Leaving this in place for future */
    for (i = 0; i < 2; i++) {
        com = strchr(ptr, ',');

        if (com)
            *com = '\0';

        switch (i) {
            case 0: model = ptr;
                break;
            case 1: rating = ptr;
                break;
            default:
                break;
        }

        if (com)
            ptr = com + 1;
    }

    if (!model)
        fatalx("Didn't get a valid ident string");

    model_set(model, rating);
}


void upsdrv_initinfo(void)
{
    int ret;
    char temp[256];

/*    ups_sync();
 *
 *    DJR 12-29-2006
 *    Looking at the ups_sync routine makes me think that ups_sync and 
ups_ident
 *    does the same thing.  Currently, I am remming it out, seems to 
work dandy
 *    without it.
 */

    ups_ident();

    printf("Detected %s %s on %s\n", dstate_getinfo("ups.mfr"),
        dstate_getinfo("ups.model"), device_path);

    /* paranoia - cancel any shutdown that might already be running */
   
    set_cps_serial();
    ser_send_pace(upsfd, UPSDELAY, "D\r"); /* Need a readback so the 
first poll doesn't fail */
    ret = ser_get_line(upsfd, temp, sizeof(temp), ENDCHAR, "", 
SER_WAIT_SEC, SER_WAIT_USEC);
   
    clr_cps_serial();
   
    set_cps_serial();
    ser_send_pace(upsfd, UPSDELAY, "C\r"); /* Need a readback so the 
first poll doesn't fail */
    ret = ser_get_line(upsfd, temp, sizeof(temp), ENDCHAR, "", 
SER_WAIT_SEC, SER_WAIT_USEC);
   
    clr_cps_serial();

    upsh.instcmd = instcmd;

    dstate_setinfo("driver.version.internal", "%s", DRV_VERSION);
    dstate_addcmd("test.battery.start");
    dstate_addcmd("test.battery.stop");
}

static int ups_on_line(void)
{
    int    i, ret;
    char    temp[256];

    for (i = 0; i < MAXTRIES; i++) {
       
        set_cps_serial();
        ser_send_pace(upsfd, UPSDELAY, "D\r");

        ret = ser_get_line(upsfd, temp, sizeof(temp), ENDCHAR, "",
            SER_WAIT_SEC, SER_WAIT_USEC);
       
        clr_cps_serial();       

        /* D must return 34 bytes starting with a # */
        if ((ret > 0) && (temp[0] == '#') && (strlen(temp) == 34)) {

            char * pos = &temp[pollstatusmap[POLL_UPSSTATUS].begin];

            if ((*pos & CPS_STAT_OL) && !(*pos & CPS_STAT_OB))
                return(1);    /* on line */

            return 0;    /* on battery */
        }

        sleep(1);
    }

    upslogx(LOG_ERR, "Status read failed: assuming on battery");

    return 0;    /* on battery */
}

void upsdrv_shutdown(void)
{
    int ret;
    char    buf[256];

/*    Don't need ups_sync() here, initialization has been done a long
 *    time ago!
 *    ups_sync();
 */

    printf("The UPS will shut down in approximately one minute.\n");

    if (ups_on_line())
        printf("The UPS will restart in about one minute.\n");
    else
        printf("The UPS will restart when power returns.\n");

    /* Although this is straight from the bestups.c driver, the UPS
     * does indeed shutdown correctly. */

    /* DJR - after hexedited the powerpanel, I believe it is
        S00R0000 instead of S01R0001    */
    /* DJR - after more testing, S00R0000 seems to kinda work,
        S01R0001 does work, and Z02 is what powerpanel uses...so... */

    set_cps_serial();
    ser_send_pace(upsfd, UPSDELAY, "Z02\r");

    ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, "",
                       SER_WAIT_SEC, SER_WAIT_USEC);

    clr_cps_serial();

    if ((ret >= 2) && (buf[0] == '#') && (buf[1] == '0'))
        upsdebugx(2,"upsdrv_shutdown: got [%s], good to shutdown", buf);
    else {       
        upsdebugx(2,"upsdrv_shutdown: got [%s], shutdown failed", buf);
        printf ("Warning: got unexpected reply to shutdown command, 
shutdown may fail\n");
    }
}

void upsdrv_updateinfo(void)
{
    char    buf[256];
    int    ret;

/*
 *    Only sending D\r here.  The leading \r is not needed
 *
 */

    set_cps_serial();
    ret = ser_send_pace(upsfd, UPSDELAY, "D\r");

    if (ret < 1) {
        ser_comm_fail("ser_send_pace failed");
        dstate_datastale();
        return;
    }

    /* these things need a long time to respond completely */

/*   
 *    usleep(200000);
 *    DJR 12-29-2006
 *    the cps 1200 doesn't seem to, the ups_on_line works without the delay
 */

    ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, "",
        SER_WAIT_SEC, SER_WAIT_USEC);
   
    clr_cps_serial();
   
    if (ret < 1) {
        ser_comm_fail(NULL);
        dstate_datastale();
        return;
    }

    if ((ret >= 34) && (ret <= 36))     {    /* removing the if to see 
if we got a #2    */
       
        upsdebugx(2,"upsdrv_updateinfo: got [%s]", buf);

    }
    else {
        upsdebugx(2,"upsdrv_updateinfo: got [%s]", buf);
        ser_comm_fail("Poll failed: short read (got %d bytes)", ret);
        dstate_datastale();
        return;
    }
   

    if (ret > 36) {
        upslogx(LOG_INFO, "String too long...");
        upsdebugx(2,"upsdrv_updateinfo: got [%s]", buf);
        ser_comm_fail("Poll failed: response too long (got %d bytes)",
            ret);
        dstate_datastale();
        return;
    }

    if (buf[0] != '#') {
        upsdebugx(2,"upsdrv_updateinfo: got [%s]", buf);
        ser_comm_fail("Poll failed: invalid start character (got %02x)",
            buf[0]);
        dstate_datastale();
        return;
    }

    ser_comm_good();

    scan_poll_values(buf);

    status_init();

}

void upsdrv_help(void)
{
}

void upsdrv_makevartable(void)
{
}

void upsdrv_banner(void)
{
    printf("Network UPS Tools -  CyberPower text protocol UPS driver %s 
(%s)\n",
        DRV_VERSION, UPS_VERSION);
    experimental_driver = 1;    /* Causes a warning message to be printed */
}

void upsdrv_initups(void)
{
    upsfd = ser_open(device_path);
    ser_set_speed(upsfd, device_path, B2400);
}

void upsdrv_cleanup(void)
{
    ser_close(upsfd, device_path);
}



-------------- next part --------------
/* cpsups.c - model specific routines for CyberPower text protocol UPSes 

   Copyright (C) 2003  Walt Holman <waltabbyh at comcast.net>
   with thanks to Russell Kroll <rkroll at exploits.org>

   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
*/

/* This driver started out as the bestups.c driver from 1.5.11 - 
 * I've hacked it up every which way to get it to function 
 * with a CPS1100AVR. Thanks go to the guys at 
 * http://networkupstools.org for creating a very nice toolset.
*/

#include "cpsups.h"

#define DRV_VERSION ".04"

static void model_set(const char *abbr, const char *rating)
{

        /* 
         * Added: Brad Sawatzky <brad+nut at lamorak.phys.virginia.edu> 02Jun04
         * NOTE: I have no idea how to set the runtime parameter... I basically
         * scaled up linearly from the 1100 and 500 entries based on the
         * 'voltage'.  The realtime runtime calculated under load looks
         * reasonable.
         */
	if (!strcmp(abbr, "#1500VA    ")) {
	        dstate_setinfo("ups.mfr", "%s", "CyberPower");
	        dstate_setinfo("ups.model", "CPS1500AVR %s", rating);
		dstate_setinfo("ups.runtime", "%s", "90");
		dstate_setinfo("ups.power.nominal", "%s", "1500");
	        return;
	}

	/* Added: Doug Reynolds 29Dec06 */
        if (!strcmp(abbr, "#BC1200    ")) {
		dstate_setinfo("ups.mfr", "%s", "CyberPower");
		dstate_setinfo("ups.model", "CPS1200VA %s", rating);
		dstate_setinfo("ups.runtime", "%s", "60");
		dstate_setinfo("ups.power.nominal", "%s", "1200");
		return;
	}
	
	if (!strcmp(abbr, "#1100VA    ")) {
	        dstate_setinfo("ups.mfr", "%s", "CyberPower");
	        dstate_setinfo("ups.model", "CPS1100VA %s", rating);
		dstate_setinfo("ups.runtime", "%s", "60");
		dstate_setinfo("ups.power.nominal", "%s", "1100");
	        return;
	}

	/* Added: Armin Diehl <diehl at ...> 14Dec04 */
	if (!strcmp(abbr, "#1000VA    ")) {
		dstate_setinfo("ups.mfr", "%s", "MicroDowell");
		dstate_setinfo("ups.model", "B.Box BP 1000 %s", rating);
		dstate_setinfo("ups.runtime", "%s", "50");
		dstate_setinfo("ups.voltage", "%s", "1000");
		return;
	}
	        
	if (!strcmp(abbr, "#825VA     ")) {
		dstate_setinfo("ups.mfr", "%s", "CyberPower");
		dstate_setinfo("ups.model", "CPS825VA %s", rating);
		dstate_setinfo("ups.runtime", "%s", "29");
		dstate_setinfo("ups.power.nominal", "%s", "825");
		return;
	}

	/* Added: Armin Diehl <diehl at ...> 14Dec04 */
	if (!strcmp(abbr, "#750VA     ")) {
		dstate_setinfo("ups.mfr", "%s", "MicroDowell");
		dstate_setinfo("ups.model", "B.Box BP 750 %s", rating);
		dstate_setinfo("ups.runtime", "%s", "29");
		dstate_setinfo("ups.voltage", "%s", "825");
		return;
	}

	if (!strcmp(abbr, "#500VA     ")) {
                dstate_setinfo("ups.mfr", "%s", "CyberPower");
                dstate_setinfo("ups.model", "OP500TE %s", rating);
                dstate_setinfo("ups.runtime", "%s", "16.5");
                dstate_setinfo("ups.power.nominal", "%s", "500");
		return;
	}

	dstate_setinfo("ups.mfr", "%s", "Unknown");
	dstate_setinfo("ups.model", "%s (%s)", abbr, rating);
	dstate_setinfo("ups.runtime", "%s", "1");
	dstate_setinfo("ups.power.nominal", "%s", "1");

	printf("Unknown model detected - please report this ID: '%s'\n", abbr);
}

static void clr_cps_serial(void)
{
	int dtr_bit = TIOCM_DTR;

	ioctl(upsfd, TIOCMBIC, &dtr_bit);
}

static void set_cps_serial(void)
{
        int dtr_bit = TIOCM_DTR;

        ioctl(upsfd, TIOCMBIS, &dtr_bit);
        tcflush(upsfd, TCIOFLUSH);
}

static int instcmd(const char *cmdname, const char *extra)
{
	/* The following commands also appear to be valid on the CPS1100 */

	if (!strcasecmp(cmdname, "test.battery.stop")) {
		ser_send_pace(upsfd, UPSDELAY, "CT\r");
		return STAT_INSTCMD_HANDLED;
	}

	if (!strcasecmp(cmdname, "test.battery.start")) {
		ser_send_pace(upsfd, UPSDELAY, "T\r");
		return STAT_INSTCMD_HANDLED;
	}

	upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
	return STAT_INSTCMD_UNKNOWN;
}


static int get_ident(char *buf, size_t bufsize)
{
	int	a, rdy, ret;
	char	dr[256];
	
/*
 *              The cyberpower monitoring program sends the first \r seperate
 *              from the identity request.  It sends a \r, waits for a #2,
 *              then sends a P4\r
 *
 *              Hopefully, I will be able to duplicate that function here.
 *		This seems to be a better way for the driver to accurately
 *		detect unknown models.  In former state, it would fail to 
 *		detect anything.
 *
 */
	
	for (a = 0; a < MAXTRIES; a++) {
		
		set_cps_serial();

		ser_send_pace(upsfd, UPSDELAY, "\r");
 		
		ret = ser_get_line(upsfd, dr, sizeof(dr), ENDCHAR, "",
			SER_WAIT_SEC, SER_WAIT_USEC);

		clr_cps_serial();

		if ((dr[0] == '#') && (dr[1] == '2')) {
			upsdebugx(2, "get_ident: got [%s], ready to poll for ups model", dr);
			rdy = 1;
			break;
			}
		else {
			upsdebugx(2, "get_ident: got [%s], instead of '#2', retrying", dr);
			rdy = 0;
			}
		}


	if (rdy) {
		for (a = 0; a < MAXTRIES; a++) {

			set_cps_serial();
			ser_send_pace(upsfd, UPSDELAY, "P4\r");
	
			ret = ser_get_line(upsfd, buf, bufsize, ENDCHAR, "",
				SER_WAIT_SEC, SER_WAIT_USEC);
				
			clr_cps_serial();

			set_cps_serial();
			ser_send_pace(upsfd, UPSDELAY, "P4\r");

			ret = ser_get_line(upsfd, buf, bufsize, ENDCHAR, "",
				SER_WAIT_SEC, SER_WAIT_USEC);

			clr_cps_serial();

			if (ret > 0)
				upsdebugx(2, "get_ident: got [%s]", buf);

			/* buf must start with # and be in the range [25-27] */

			if ((ret > 0) && (buf[0] == '#') && (strlen(buf) >= 25) && (strlen(buf) <= 50)) {
				rdy = 1;
				break;
				}
			else {
				rdy = 0;
				upsdebugx(2, "get_ident: got [%s], instead of '#2', retrying", buf);
				}
			}
		}
	
	if (rdy) {
		for (a = 0; a < MAXTRIES; a++) {

			set_cps_serial();
			ser_send_pace(upsfd, UPSDELAY, "P3\r");

			ret = ser_get_line(upsfd, dr, sizeof(dr), ENDCHAR, "",
				SER_WAIT_SEC, SER_WAIT_USEC);

			clr_cps_serial();

			if ((ret > 0) && (dr[0] == '#') && (strlen(dr) > 2)) {

				rdy = 1;
				upsdebugx(2, "get_ident: P3 smart mode ok");
				break;
				}
			else {
				rdy = 0;
				upsdebugx(2, "get_ident: got [%s], not smart mode ok", dr);
				}
			}
		}

	if (rdy) {
		for (a = 0; a < MAXTRIES; a++) {
		
		set_cps_serial();
		ser_send_pace(upsfd, UPSDELAY, "P2\r");

		ret = ser_get_line(upsfd, dr, sizeof(dr), ENDCHAR, "",
			SER_WAIT_SEC, SER_WAIT_USEC);

		clr_cps_serial();

		if ((ret > 0) && (dr[0] == '#') && (strlen(dr) > 2)) {

			rdy = 1;
			upsdebugx(2, "get_ident: P2 smart mode ok");
			break;
			}
		else {
			rdy = 0;
			upsdebugx(2, "get_ident: got [%s], not smart mode ok", dr);
			}
		}	
	}

	if (rdy) {
		for (a = 0; a < MAXTRIES; a++) {


		set_cps_serial();
		ser_send_pace(upsfd, UPSDELAY, "P1\r");

		ret = ser_get_line(upsfd, dr, sizeof(dr), ENDCHAR, "",
			SER_WAIT_SEC, SER_WAIT_USEC);

		clr_cps_serial();

		if ((ret > 0) && (dr[0] == '#') && (strlen(dr) > 2)) {

			rdy = 1;
			upsdebugx(2, "get_ident: P1 smart mode ok");
			break;
			}
		else {
			rdy = 0;
			upsdebugx(2, "get_ident: got [%s], not smart mode ok", dr);                        
			}
		}
	}

	if (rdy) {
		for (a = 0; a < MAXTRIES; a++) {
		
		set_cps_serial();
		ser_send_pace(upsfd, UPSDELAY, "P7\r");

		ret = ser_get_line(upsfd, dr, sizeof(dr), ENDCHAR, "",
			SER_WAIT_SEC, SER_WAIT_USEC);

		clr_cps_serial();

		if ((ret > 0) && (dr[0] == '#') && (strlen(dr) > 2)) {

			rdy = 1;
			upsdebugx(2, "get_ident: P7 smart mode ok");
			break;
			}
		else {
			rdy = 0;
			upsdebugx(2, "get_ident: got [%s], not smart mode ok", dr);
			}
		}
	}

	if (rdy) {
		for (a = 0; a < MAXTRIES; a++) {

		set_cps_serial();
		ser_send_pace(upsfd, UPSDELAY, "P6\r");

		ret = ser_get_line(upsfd, dr, sizeof(dr), ENDCHAR, "",
			SER_WAIT_SEC, SER_WAIT_USEC);

		clr_cps_serial();

		if ((ret > 0) && (dr[0] == '#') && (strlen(dr) > 2)) {

			rdy = 1;
			upsdebugx(2, "get_ident: P6 smart mode ok");
			break;
			}
		else {
			rdy = 0;
			upsdebugx(2, "get_ident: got [%s], not smart mode ok", dr);
			}
		}
	}

	if (rdy) {
		for (a = 0; a < MAXTRIES; a++) {
		
		set_cps_serial();
		ser_send_pace(upsfd, UPSDELAY, "P8\r");

		ret = ser_get_line(upsfd, dr, sizeof(dr), ENDCHAR, "",
			SER_WAIT_SEC, SER_WAIT_USEC);

		clr_cps_serial();

		if ((ret > 0) && (dr[0] == '#') && (strlen(dr) > 2)) {

			rdy = 1;
			upsdebugx(2, "get_ident: P8 smart mode ok");
			break;
			}
		else {
			rdy = 0;
			upsdebugx(2, "get_ident: got [%s], not smart mode ok", dr);
			}
		}
	}


	if (rdy) {
		for (a = 0; a < MAXTRIES; a++) {
			
			set_cps_serial();
			ser_send_pace(upsfd, UPSDELAY, "P9\r");
			
			ret = ser_get_line(upsfd, dr, sizeof(dr), ENDCHAR, "",
				SER_WAIT_SEC, SER_WAIT_USEC);

			clr_cps_serial();
			
			if ((ret > 0) && (dr[0] == '#') && (strlen(dr) > 2) && (strlen(dr) < 8)) {
				
				rdy = 1;
				upsdebugx(2, "get_ident: P9 smart mode ok");
				break;
				}
			else {
				rdy = 0;
				upsdebugx(2, "get_ident: got [%s], not smart mode ok", dr);
				} 

			}
		}	

	
	
	if (!rdy) {
		upslogx(LOG_INFO, "Giving up on hardware detection after %d tries", MAXTRIES);
		return 0;
		}
	else	{
		upsdebugx(2, "get_ident: ups initialized and ready");
		return 1;
		}
}

static int scan_poll_values(char *buf)
{
	char values[20][200], *pos;
	int i = 0, battremain, length, rseconds;
	double rminutes;

/*	These are used to hold status of UPS.
 *	tmp1 = online/onbattery status
 */
	char *tmp1 = &buf[pollstatusmap[POLL_UPSSTATUS].begin];

	while ((pollstatusmap[i].end != 0))
	{
		pos = &buf[pollstatusmap[i].begin];
		length = pollstatusmap[i].end + 1 - pollstatusmap[i].begin;
		strncpy(values[i],pos,length);
		i++;
	}

	if ((*tmp1 & CPS_STAT_OL) && !(*tmp1 & CPS_STAT_OB)) 
		status_set("OL");

	if (*tmp1 & CPS_STAT_OB) 
		status_set("OB");

	if (*tmp1 & CPS_STAT_CAL)
		status_set("CAL");

	if (*tmp1 & CPS_STAT_LB) 
		status_set("LB");

	if (*tmp1 == 0)
		status_set("OFF");


	pos = values[3];
        battremain = strtol(dstate_getinfo("ups.power.nominal"),NULL,10) * (strtod(pos,NULL)/100);

	/* Figure out runtime minutes */
	rminutes = strtod(dstate_getinfo("ups.runtime"),NULL) * 
		((battremain * ( 1 - (strtod (values[2],NULL) / 100 ))) / 
		 strtol(dstate_getinfo("ups.power.nominal"),NULL,10));
	rseconds = ((int)(rminutes*100) - ((int)rminutes)*100) * 0.6 ;

        dstate_setinfo("input.voltage", "%g", strtod(values[0],NULL));
        dstate_setinfo("output.voltage", "%g", strtod(values[1],NULL));
        dstate_setinfo("ups.load", "%li", strtol(values[2],NULL,10));
        dstate_setinfo("input.frequency", "%g", strtod(values[5],NULL));
        dstate_setinfo("ups.temperature", "%li", strtol(values[4],NULL,10));
	dstate_setinfo("battery.charge", "%02.1f", strtod(values[3],NULL));
	dstate_setinfo("battery.runtime", "%2.0f:%02d", rminutes, rseconds);

	status_commit();
	dstate_dataok();
	return 0;
}

static void ups_ident(void)
{
	char	buf[256], *ptr, *com, *model, *rating;
	int	i;

	if (!get_ident(buf, sizeof(buf)))
		fatalx("Unable to detect a CyberPower text protocol UPS");

	model = rating = NULL;

	ptr = buf;

	/* Leaving this in place for future */
	for (i = 0; i < 2; i++) {
		com = strchr(ptr, ',');

		if (com)
			*com = '\0';

		switch (i) {
			case 0: model = ptr;
				break;
			case 1: rating = ptr;
				break;
			default:
				break;
		}

		if (com)
			ptr = com + 1;
	}

	if (!model)
		fatalx("Didn't get a valid ident string");

	model_set(model, rating);
}


void upsdrv_initinfo(void)
{
	int ret;
	char temp[256];

/*	ups_sync(); 
 *
 *	DJR 12-29-2006
 *	Looking at the ups_sync routine makes me think that ups_sync and ups_ident
 *	does the same thing.  Currently, I am remming it out, seems to work dandy
 *	without it.
 */

	ups_ident();

	printf("Detected %s %s on %s\n", dstate_getinfo("ups.mfr"),
		dstate_getinfo("ups.model"), device_path);

	/* paranoia - cancel any shutdown that might already be running */
	
	set_cps_serial();
	ser_send_pace(upsfd, UPSDELAY, "D\r"); /* Need a readback so the first poll doesn't fail */
	ret = ser_get_line(upsfd, temp, sizeof(temp), ENDCHAR, "", SER_WAIT_SEC, SER_WAIT_USEC);
	
	clr_cps_serial();
	
	set_cps_serial();
	ser_send_pace(upsfd, UPSDELAY, "C\r"); /* Need a readback so the first poll doesn't fail */
	ret = ser_get_line(upsfd, temp, sizeof(temp), ENDCHAR, "", SER_WAIT_SEC, SER_WAIT_USEC);
	
	clr_cps_serial();

	upsh.instcmd = instcmd;

	dstate_setinfo("driver.version.internal", "%s", DRV_VERSION);
	dstate_addcmd("test.battery.start");
	dstate_addcmd("test.battery.stop");
}

static int ups_on_line(void)
{
	int	i, ret;
	char	temp[256];

	for (i = 0; i < MAXTRIES; i++) {
		
		set_cps_serial();
		ser_send_pace(upsfd, UPSDELAY, "D\r");

		ret = ser_get_line(upsfd, temp, sizeof(temp), ENDCHAR, "",
			SER_WAIT_SEC, SER_WAIT_USEC);
		
		clr_cps_serial();		

		/* D must return 34 bytes starting with a # */
		if ((ret > 0) && (temp[0] == '#') && (strlen(temp) == 34)) {

			char * pos = &temp[pollstatusmap[POLL_UPSSTATUS].begin];

			if ((*pos & CPS_STAT_OL) && !(*pos & CPS_STAT_OB))
				return(1);    /* on line */

			return 0;	/* on battery */
		}

		sleep(1);
	}

	upslogx(LOG_ERR, "Status read failed: assuming on battery");

	return 0;	/* on battery */
}

void upsdrv_shutdown(void)
{
	int ret;
	char    buf[256];

/*	Don't need ups_sync() here, initialization has been done a long
 *	time ago!
 *	ups_sync();
 */

	printf("The UPS will shut down in approximately one minute.\n");

	if (ups_on_line())
		printf("The UPS will restart in about one minute.\n");
	else
		printf("The UPS will restart when power returns.\n");

	/* Although this is straight from the bestups.c driver, the UPS
	 * does indeed shutdown correctly. */

	/* DJR - after hexedited the powerpanel, I believe it is
		S00R0000 instead of S01R0001	*/
	/* DJR - after more testing, S00R0000 seems to kinda work,
		S01R0001 does work, and Z02 is what powerpanel uses...so... */

	set_cps_serial();
	ser_send_pace(upsfd, UPSDELAY, "Z02\r");

	ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, "",
					   SER_WAIT_SEC, SER_WAIT_USEC);

	clr_cps_serial();

	if ((ret >= 2) && (buf[0] == '#') && (buf[1] == '0'))
		upsdebugx(2,"upsdrv_shutdown: got [%s], good to shutdown", buf);
	else {		
		upsdebugx(2,"upsdrv_shutdown: got [%s], shutdown failed", buf);
		printf ("Warning: got unexpected reply to shutdown command, shutdown may fail\n");
	}
}

void upsdrv_updateinfo(void)
{
	char	buf[256];
	int	ret;

/*
 *	Only sending D\r here.  The leading \r is not needed
 *
 */

	set_cps_serial();
	ret = ser_send_pace(upsfd, UPSDELAY, "D\r");

	if (ret < 1) {
		ser_comm_fail("ser_send_pace failed");
		dstate_datastale();
		return;
	}

	/* these things need a long time to respond completely */

/*	
 *	usleep(200000);
 *	DJR 12-29-2006
 *	the cps 1200 doesn't seem to, the ups_on_line works without the delay
 */

	ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, "",
		SER_WAIT_SEC, SER_WAIT_USEC);
	
	clr_cps_serial();
	
	if (ret < 1) {
		ser_comm_fail(NULL);
		dstate_datastale();
		return;
	}

	if ((ret >= 34) && (ret <= 36))	 {	/* removing the if to see if we got a #2    */
		
		upsdebugx(2,"upsdrv_updateinfo: got [%s]", buf);

	} 
	else {
		upsdebugx(2,"upsdrv_updateinfo: got [%s]", buf);
		ser_comm_fail("Poll failed: short read (got %d bytes)", ret);
		dstate_datastale();
		return;
	}
	

	if (ret > 36) {
		upslogx(LOG_INFO, "String too long...");
		upsdebugx(2,"upsdrv_updateinfo: got [%s]", buf);
		ser_comm_fail("Poll failed: response too long (got %d bytes)",
			ret);
		dstate_datastale();
		return;
	}

	if (buf[0] != '#') {
		upsdebugx(2,"upsdrv_updateinfo: got [%s]", buf);
		ser_comm_fail("Poll failed: invalid start character (got %02x)",
			buf[0]);
		dstate_datastale();
		return;
	}

	ser_comm_good();

	scan_poll_values(buf);

	status_init();

}

void upsdrv_help(void)
{
}

void upsdrv_makevartable(void)
{
}

void upsdrv_banner(void)
{
	printf("Network UPS Tools -  CyberPower text protocol UPS driver %s (%s)\n",
		DRV_VERSION, UPS_VERSION);
	experimental_driver = 1;	/* Causes a warning message to be printed */
}

void upsdrv_initups(void)
{
	upsfd = ser_open(device_path);
	ser_set_speed(upsfd, device_path, B2400);
}

void upsdrv_cleanup(void)
{
	ser_close(upsfd, device_path);
}



More information about the Nut-upsdev mailing list