家里有个自己拼凑的nas需要防止断电不正常关机,因此购买了施耐德后背式BP650CH,之所以选这款是因为带了串口,串口终究还是很方便的东西。不管linux还是window还是其他系统都能够使用,通过串口直接获得ups的信息,就不需要关心操作系统是什么了。
BP650CH的串口协议参考这篇文档是BP650CHBP1000CH串口通信协议.pdf-原创力文档 (book118.com)
默认波特兰2400,8bit数据,1bit停止位,无检验。SecureCRT设置如下
由于这个施耐德的UPS串口并非终端交互式的,因此我们不适合用SecureCRT,采用其他输入和输出分离的串口助手。
我使用Arduino IDE中的串口助手,设置如下
按照手册,首先需要登录到UPS,方法就是输入M回车,那么串口会返回一个V
接着输入QS回车,这个命令是检索状态。注意施耐德的串口交互命令都是回车结尾。
反正QS命令返回的结果格式为:
(是结果的开始,然后后面的数值用空格分开
216.1=输入电压216.1伏特(有效值rms)
216.1=输入故障电压216.1伏特(有效值rms)
216.1=输出电压216.1伏特(有效值rms)
000=输出负载,因为我UPS没有接负载,所以就是0,单位是百分比
50.0=输出电压的频率是50Hz
13.5=电池电压13.5V(有效值rms)
--.-=内部温度,似乎没法显示内部温度
00001001=UPS状态字节b7~b0
b6为高位的话指示电池电压低
b4为高位指示UPS故障
剩下的看手册
测试1:关闭UPS的市电输入,蜂鸣器10秒叫一次,QS命令检索状态发现UPS的状态字节最高位变成了1,这个时候就可以通知用户进行关机了
测试2:使用破旧没有电池的笔记本电脑装linux系统做断电自动关机的测试。
将USB转串口线插到电脑,lsusb发现串口PL2303驱动
ls /dev/tty*发现串口设备ttyUSB0
安装minicom软件:yum install minicom
然后minicom -s进入设置,选择Serial port setup。然后设置如下
然后save setup as dfl,下次就不用设置了
然后exit from minicom
接着输入minicom
输入M登录后,输入QS就可以查询数据了
退出minicom方法,ctrl+A后,然后按Z,然后按X,然后yes
但是minicom是交互式的,因此我们需要自己写一个程序实现ups的状态扫描来实现关机
#include<stdio.h>
#include<fcntl.h>
#include<stdlib.h>
#include<unistd.h>
#include<termios.h>
#include<string.h>
#include<sys/select.h>
int serialDevFD;//串口设备描述符
char *serialDev = "/dev/ttyUSB0";
void serialDevOpen()
{
serialDevFD = open(serialDev,O_RDWR|O_NOCTTY|O_NDELAY);//读写方式打开串口设备,后面两个属性不用管
if(serialDevFD<0)
{
printf("Serial port device %s open failed!!!\n",serialDev);
perror(serialDev);
exit(1);
}
printf("Serial port device %s open success, file descriptor = %d\n",serialDev,serialDevFD);
}
void serialDevClose()
{
close(serialDevFD);
printf("Serial port device %s close success\n",serialDev);
}
//延时n秒
void sleepSecond(int n)
{
sleep(n);
}
//串口通信参数配置
void serialDevCfg()
{
struct termios newT;//新的配置信息结构体
memset(&newT,0,sizeof(newT));//结构体清零
cfsetispeed(&newT,B2400);//设置input/output波特率都是2400
cfsetospeed(&newT,B2400);
//
newT.c_cflag |= (CLOCAL|CREAD);
newT.c_cflag &=~CSIZE;
//设置数据位8位
newT.c_cflag |= CS8;
//设置无校验
newT.c_cflag &= ~PARENB;
newT.c_iflag &= ~INPCK;
//设置停止位1位
newT.c_cflag &= ~CSTOPB;
//
newT.c_cc[VTIME]=0;
newT.c_cc[VMIN]=0;
tcflush(serialDevFD,TCIOFLUSH);
if(tcsetattr(serialDevFD,TCSANOW,&newT)!=0){
perror("set Baud failed");
exit(1);
}else {
printf("Serial port device %s set params success\n",serialDev);
}
}
//登录和监控ups
char *UPS_LOGIN = "M\r";
char *UPS_STATUS = "QS\r";
void serialMonitor()
{
fd_set rds;
int ret;
int len;
char buf;
char arr[300];
write(serialDevFD,UPS_LOGIN,2);//登录
int loginIndex = 0;
while(1)
{
FD_ZERO(&rds);
FD_SET(serialDevFD,&rds);
/*调用select检查是否能够从/dev/input/event0设备读取数据*/
ret = select(serialDevFD+1, &rds, NULL, NULL, NULL );
if ( ret < 0 )
{
perror( "select" );
exit(2);
}
/*能够读取到数据*/
else if (FD_ISSET(serialDevFD,&rds))
{
len = read(serialDevFD,&buf,1);
if(len==1)
{
arr[loginIndex++] = buf;
if(loginIndex==2)
{
if(arr[1]=='\r'){
arr[1]='<';
arr[2] = 'c';
arr[3]='r';
arr[4] = '>';
}
arr[5] = '\0';
printf("接收到UPS登录响应=%s\n",arr);
break;
}
}
}
}
//
int duration = 3;//监测周期,单位秒
int count = 0;//计数器,检测到断电后再检测2次如果真的没有电就关机
int statusIndex = 0;
write(serialDevFD,UPS_STATUS,3);
while(1)
{
FD_ZERO(&rds);
FD_SET(serialDevFD,&rds);
/*调用select检查是否能够从/dev/input/event0设备读取数据*/
ret = select(serialDevFD+1, &rds, NULL, NULL, NULL );
if ( ret < 0 )
{
perror( "select" );
exit(2);
}
/*能够读取到数据*/
else if (FD_ISSET(serialDevFD,&rds))
{
len = read(serialDevFD,&buf,1);
if(len==1)
{
if(buf=='\r'){
//printf("<cr>\n");
arr[statusIndex] = '\0';
printf("接收到ups状态响应=%s<cr>\t",arr);
int pass = 1;
/* ups状态数据合法性检查 */
//检查长度是否是46个字符
if(statusIndex!=46) pass = 0;
//检查第一个字符是不是左括号
if(arr[0]!='(') pass = 0;
if(pass==1)
{
printf("ups状态数据合法,");
//检测市电是否发生了断电
if(arr[38]=='1')
{
printf("发生了断电\n");
count++;
if(count==3)
{
printf("ups发生断电,执行关机命令\n");
system("shutdown -h now");
}
}else
{
printf("市电输入正常\n");
count = 0;
}
}else
{
printf("ups状态数据不合法,跳过\n");
}
sleepSecond(duration);
statusIndex = 0;
write(serialDevFD,UPS_STATUS,3);
}else{
//printf("%c",buf);
arr[statusIndex++] = buf;
}
}
}
}
}
int main(){
serialDevOpen();
serialDevCfg();
serialMonitor();
serialDevClose();
return 0;
}
Makefile如下:
CROSS=
TARGET=upsMonitor
SRC=main.c
all: $(TARGET)
$(TARGET):$(SRC)
$(CROSS)gcc -std=c99 -o $(TARGET) $(SRC)
$(CROSS)strip $(TARGET)
clean:
@rm -vf $(TARGET) *.o *~