CSK.Blog--个人原创Weblog

« FlashSite V2++发下牢骚 »

Linux下监控的Santak MT-500pro系列UPS信息

这段时间一直在"筹建"以前提到过的智能化家庭系统(IHES)。前不久购买并配置了500G的RAID 1存储阵列服务器,为了保证数据安全,同时又购买了Santak MT-500pro智能型UPS。

这里说智能型可能不是很专业,其实就是说该UPS提供了相关的接口允许从服务器上读取出UPS的相关状态信息。比如:

外部交流供电电压、负载端电压、负载功率、蓄电池电压等信息

不过,其实最需要关心的就是希望能够在发生断电情况下ups能即时告知。以便服务器机组能在UPS逆变供电实效前自动关机。毕竟不可能让我24小时监控家中服务器状况。

以上可以算是背景介绍了,现在面临的问题是,这个品牌的UPS虽然在官方提供了相关的监控和管理软件Winpower。不过使用后,存在许多不足:

  1. 程序采用java (1.14) 开发,甚至连与ups通讯的部分也完全采用java
  2. 该程序实现上十分低效,监控目模块间通讯需要采用java rmi
  3. 没有命令行接口,可制订性差

当然这些问题不是所有人都会抱怨的,不过目前考虑到家里的中央服务器还采用P3 CPU, 512mb内存,以及没有安装X Server环境。纯命令行下WinPower完全无法工作。同时采用java的实现虽然从厂家角度降低了porting成本,不过绝对是一个低效的实现。我测试过仅仅开启了Winpower中的监控模块,java进程就消耗了近20Mb的资源。同时还不考虑低效的实现(我逆向工程过其class文件,实现很糟糕)。仅仅为了监控ups信息而做出如此的资源消耗实在不值得。

另一方面,如果选用APC品牌的UPS,linux下则有现成的Apcupsd。当然东西都买下了,后悔也没用。于是,我实现一个满足我需求的ups监控程序。这里提供一个原形程序的代码以及整个整个制作过程的叙述。支持的型号是Santak MT-500pro。不过我相信通过源代码以及按照我的制作思路,其他型号也很容易做出对应的程序。原形程序是纯命令行的,很容易用bash shell写一个断电后一定时间内控制服务器自动关机的daemon脚本。

下面是制作介绍部分,没兴趣看得话代码在文章末尾给出了,请自行编译使用。Windows版本的也可以很容易修改而成。

MT-500pro的相关信息:

毕竟不是给人家做广告,这里什么技术参数就不给了。

该UPS采用串口(COM)提供自身的状态信息。串口的通讯采用2400bps速率,目前已知的状态信息有如下2种形式:

  • "#220.0 004 12.00 50.0\r"
  • "(231.4 231.0 231.4 011 50.1 13.9 25.0 00001001\r"

这里先暂时不介绍相关的通讯协议。在上面的状态信息中,第一条应当为该UPS的标准参数(即正常情况下理论值)。这几个数据每次都是固定不变的。其含义依次为:

<输出(或输出电压)> <未知> <蓄电池电压> <输出(或输入)频率>

而第二项则为当前UPS的实际数据,这也是我需要关心的内容。其可能含义依次为:

<外部电压> <内部电压(具体未知)> <输出电压> <负载率%> <电源工作频率(Hz)> <蓄电池电压> <UPS机内温度(摄氏度)> <状态标志>

这里需要对状态标志做下介绍,因为我在逆向分析时无法重现所有可能情况,这里只给出我知道的几个标志位:

00001001
|     +--- UPS输出关闭位
+--------- 外部供电失效位

虽然只知道2个标志位含义,不过我想应该足够了,最高位表示当前交流供电是否异常,如果为1就表示断电了。UPS输出关闭位有效则表示当前UPS没有工作。

知道这些格式信息后,下面的问题就是如何知道相关的请求代码,即通讯协议。

监听串口通讯:

我曾想过通过把Winpower逆向工程来研究通讯协议,毕竟java 1.5以前的class文件可以很容易被反汇编(应该说是反代码)。不过后来放弃了,这里就不多说了...

目前采用的办法是监听串口的通讯。这里介绍一个相对使用什么本机COM1与COM2对接等方法简单多得手段。在Windows平台下可以采用一款叫做HHD Free Serial Port Monitor的免费软件。或者Sysinternal上也提供了相关工具。他们都通过hook IOCTRL函数来截获COM的通讯内容。

使用这类工具,开启Winpower后,相关的通讯协议也一目了然。其实根本不能算协议,十分的简单:

 Request: 2009-2-7 20:00:19.95264 (+0.9013 seconds)

  46 0D                                             F.

 Answer: 2009-2-7 20:00:19.01264 (+0.0601 seconds)

  23 32 32 30 2E 30 20 30 30 34 20 31 32 2E 30 30   #220.0 004 12.00
  20 35 30 2E 30 0D                                  50.0.

 Request: 2009-2-7 20:00:20.97364 (+0.8913 seconds)

  51 31 0D                                          Q1.

 Answer: 2009-2-7 20:00:20.03364 (+0.0601 seconds)

  28 32 32 35 2E 39 20 32 32 35 2E 39 20 30 30 30   (225.9 225.9 000
  2E 30 20 30 30 30 20 30 30 2E 30 20 31 33 2E 38   .0 000 00.0 13.8
  20 32 35 2E 30 20 30 30 30 30 31 30 31 31 0D       25.0 00001011.

 

因此,上面提到的2组信息分别对应的请求数据为:

"F\r"

"Q1\r"

Linux下的轻量级监控程序:

有了上面的分析结果后,很容易的就能自己编写一个监控程序。为了各位方便,我提供一个简单的原形程序。用法如下:

# ./upsread [port name]

如果忽略了port name,则默认采用/dev/ttyS0。 程序会先输出第一项UPS信息,然后以1秒为间隔打印出第二项状态信息,如:

./upsread
#220.0 004 12.00 50.0
(233.9 234.3 233.9 011 50.1 13.9 25.0 00001001
(234.3 233.9 234.3 011 50.1 13.8 25.0 00001001
(234.3 234.3 233.9 011 50.1 13.9 25.0 00001001
(234.3 234.3 234.3 011 50.1 13.9 25.0 00001001

 

程序代码:

/******************************************************************
 *   A simple C program to retrieve the Santak(R) UPS Data
 *
 *   INTRODUCTION:
 *   ~~~~~~~~~~~~~
 *   Since the UPS monitor software WinPower provided by
 *   Santak is based on JAVA. Such implementation wastes
 *   memory and will bring troubles while running under
 *   some legacy systems.
 *
 *   So the author wrote this *light-weight * program for
 *   anyone who needs to monitor the UPS status using
 *   shell-script.
 *
 *   This program is very simple, it just requests the UPS
 *   to provide its status info via the serial port and
 *   output the response string directly to stdout.
 *
 *   The UPS will provides the following two types of status:
 *   1) standard status
 *   ----------------
 *   Request Data: "F\r"
 *   Response:     #220.0 004 12.00
 *
 *   2) current status
 *   ----------------
 *   Request Data: "Q1\r"
 *   Response:     (228.8 228.8 228.8 011 50.1 13.8 25.0 00001001
 *   
 *   You may easily decode the means of the status info
 *   with the helps of WinPower.
 *
 *   Currently, only the following model(s) has been tested:
 *   * MT-pro UPS(500/1000VA)
 *  
 *   Author Info:
 *   Shikai Chen (
csk@live.com )
 *  
http://www.csksoft.net
 *
 *   Version info:
 *   Feb.07/2009
 *         ------ First version, only supports the MT-500pro model
 *
 ******************************************************************/

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>

#define UPS_COM_BRATE B2400
#define UPS_COM_DEFAULT "/dev/ttyS0"


#define WAIT_TIME 600
#define MAX_LOOP_TIME 100


//command the requires the ups to provide the standard power info
static const char * UPSCMD_STANDARD = "F\r";

//command the requires the ups to provide the current power status
static const char * UPSCMD_CURRENT  = "Q1\r";

 


int write_n_read
(
 int  fd,
 const  char * inbuf, unsigned int insize,
 char * outbuf, unsigned int outsize
)

{
 int nread = 0;
 int nsize = 0;
 int looptime = 0;
 write(fd,inbuf,insize);

 while((looptime++)<MAX_LOOP_TIME) {
  usleep(WAIT_TIME); //wait for response

  while((nsize=read(fd,outbuf+nread, outsize - nread))>0)
  {
   nread += nsize;

   if ( outbuf[ nread -1] == '\r' ) goto READ_FIN;
   if ( nread >= outsize) goto READ_FIN;
   
  }
 }

READ_FIN:
 return nread;
}

void set_com_prop( int fd)
{
 struct termios opt;
 
 tcgetattr(fd,&opt);

 cfsetispeed(&opt,UPS_COM_BRATE);
 cfsetospeed(&opt,UPS_COM_BRATE);

 opt.c_cflag|=(CLOCAL | CREAD);

 opt.c_cflag &=~PARENB;
 opt.c_cflag &=~CSTOPB;
 opt.c_cflag &=~CSIZE;
 opt.c_cflag |=CS8;

 opt.c_lflag &= ~(ICANON|ECHO|ECHOE|ISIG);
 opt.c_oflag &=~OPOST;

 opt.c_cc[VTIME]=150;
 opt.c_cc[VMIN]=1;

 tcflush(fd,TCIFLUSH);

 tcsetattr(fd,TCSANOW,&opt);
}


int main( int argc, char * argv[])
{
 char recv_buff[512] = {0};
 int  fd             = 0;
 int  recv_len       = 0;
 
 char *target_port = UPS_COM_DEFAULT;
 
 //parsing argument...
 
 if ( argc > 1 ) target_port = argv[1];

 fd=open(target_port,O_RDWR|O_NOCTTY|O_NDELAY);

 
 set_com_prop(fd);
 //init connection:
 write_n_read( fd, UPSCMD_STANDARD,
        strlen( UPSCMD_STANDARD),
        recv_buff, sizeof(recv_buff));

 recv_len = write_n_read( fd,
    UPSCMD_STANDARD,
    strlen( UPSCMD_STANDARD),
    recv_buff, sizeof(recv_buff));
  
 if ( recv_len > 0 )
 {
  recv_buff[recv_len-1] = 0;
  fprintf(stdout, "%s\n", recv_buff);
 }

 while (1){

  recv_len = write_n_read( fd,
    UPSCMD_CURRENT,
    strlen( UPSCMD_CURRENT),
    recv_buff, sizeof(recv_buff));
  
  if ( recv_len > 0 )
  {
   recv_buff[recv_len - 1] = 0;

   fprintf(stdout, "%s\n", recv_buff);
  }
  sleep(1);
 }

 close(fd);
 return 0;
}

 

  • quote 2.ztq
  • 我们lab的ups也是串口,不过一直没跟server连起来。。。开学还得买根串口线去,唉
  • 2/11/2009 1:01:18 PM 回复该留言

发表评论:

注意:为了有效防止SPAM,任何含有http://字样的消息会被阻止发布同时,本站仅供技术交流,请不要讨论任何政治敏感话题或者低级趣味问题。

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

日历

最新评论及回复

最近发表

Copyright Shikai Chen 2000-2012. Powered By Z-Blog(CSK Modified)