【逗老师带你学IT】PRTG监控通过Python通过串口监控UPS运行状态,PRTG值查询功能定义

本文主要介绍,如何通过串口获取UPS主机的运行状态,并通过PRTG统计监控和告警。
不同UPS主机厂的串口通信协议不同,但是市面上有一种比较通用的协议,如果你使用的是EATON,山特等等国产UPS,大概率用的都是Q1协议。
本文涉及的知识点:

1、UPS串口通信分析
2、python中的pyserial模块使用。
3、PRTG的lookup值查询功能定义

本文最终实现效果如下:
在这里插入图片描述

一、UPS串口协议分析

原则上,每家UPS主机厂都多多少少会有一些自己的私有协议。开发文档也不一定公开。但是我们可以稍微分析一下,基本都可以分析得出每家主机厂的通信协议。
本文一一款山特的国产UPS举例,说明如何分析串口通信协议。

1、搭建抓包环境

说到网络抓包,大家都很熟悉。但是如何对串口通信进行抓包?
一种办法是通过串口抓包软件,对本机串口进行抓包。例如Device Monitoring Studio
在这里插入图片描述
软件功能比较强大,是收费软件,免费试用15天。从截图可以看出,选择了通信端口以后,可以很轻松监控串口数据。另外从界面上看它还有数据统计等很多其它功能,我没有全部试用,就不多介绍了。需要注意一点,它的启动按钮在界面右侧下方。

另一种办法是如果使用了串口转以太网盒子,可以在网络侧进行抓包。或者利用虚拟串口软件自带的抓包功能。
在这里插入图片描述

2、开始抓包

解决了抓包问题后,我们开始尝试通过厂商自带的方式与UPS尝试通信。
因为原厂的管理软件是肯定可以正常可以与UPS通信的,所以我们的思路是尝试去分析原厂软件是如何管理UPS的,并以此设计和编写我们自己的监控脚本。
首先用最正经的方式,下载原厂的UPS管理软件,安装,连接UPS。
在这里插入图片描述
当通过原厂的管理软件可以读到UPS后,我们就开始分析此时COM口上的通信内容,并以此分析主机的串口通信协议。

3、分析报文内容

在原厂管理软件在与UPS主机通信的时候,我们抓到了以下关键内容。
在这里插入图片描述
问询帧1:
HEX:
51 31 0D
ASCII:
Q1
应答帧1:
HEX:
28 32 33 37 2E 31 20 32 33 37 2E 39 20 32 32 30 2E 30 20 30 30 36 20 34 39 2E 39 20 32 2E 32 39 20 32 38 2E 38 20 30 30 30 30 30 30 30 31 0D
ASCII:
(237.1 237.9 220.0 006 49.9 2.29 28.8 00000001

问询帧2:
HEX:
46 0D
ASCII:
F
应答帧2:
HEX:
23 32 32 30 2E 30 20 31 33 36 20 31 39 32 2E 30 20 35 30 2E 30 0D
ASCII:
#220.0 136 192.0 50.0

这其中(237.1 237.9 220.0 006 49.9 2.29 28.8 00000001的这一句,是不是看起来就很像是电压和功率等数据呢?而且,还是用ASCII编码,肉眼可见。
配合原厂的管理软件,多观察一会抓包数据和软件显示的数据,我们大致可以分析出这个应答帧中各个字段的含义。
笔者直接粘分析结果。同时对于市面上大部分用0x51 31 0D做请求帧的UPS,应答帧的解释都是差不多的。

(MMM.MNNN.NPPP.PQQQRR.RS.SSTT.TU
(237.1237.9220.000649.92.2928.800000001
起始码0x28输入电压输入故障电压输出电压负载%市电频率Hz电池电压温度状态码

关于故障输入电压:
对于在线式UPS而言,此字段保持位上一次电压瞬变前的电压。例如市电中断时,此字段保持为断电前的输入电压,并一直维持到下一次查询。

关于电池电压:
在线式UPS此电压为单体电池电压。电池组总电压请自行计算乘以电池数量乘以电池cell数量。总电池组用了16节12v铅酸蓄电池串联,每个铅酸蓄电池含6个单体电池。所以总电池组电压是2.29v * 6 * 16=219.84v

关于状态码:
Bit7 1 : Utility Fail ( Immediate )
Bit6 1 : Battery Low
Bit5 1 : Bypass/Boost Active
Bit4 1 : UPS Failed
Bit3 1 : UPS Type is Standby (0 is On-line)
Bit2 1 : Test in Progress
Bit1 1 : Shutdown Active
Bit0 1 : Speaker Config ON

二、python中serial模块的使用

有了数据和解释。接下来写代码。serial模块默认自带,一般不需要单独pip安装
本文给出一个可以用于PRTG前端展示的示例代码,详细解释看注释。

import serial
from time import sleep
import json
import re
import sys
#PORT = '/dev/cu.usbserial-AK08ROD4'
data = json.loads(sys.argv[1])
params=str(data['params']).replace("'",'"')
params = json.loads(params)
PORT = params['PORT']
#PRTG调用时,我们将COM口的编号通过['params']['PORT']字段传递进来。

def get_serial_data(serial_session):
	try:
		serial_command = bytes.fromhex('51 31 0D')#16进制格式的问询指令
		serial_session.write(serial_command)
		#向UPS发送问询指令
		sleep(1)
		data =data = serial_session.read_all().decode("gbk")
		#读取回显
		serial_session.close()
	except Exception as err:
		return '(000 000 000 000 000 000 000 00000001'
	else:
		return data


def sort_serial_data(serial_data):
	#整理数据
	try:
		data=serial_data.split()
		#原始数据切片
		data[0]=data[0].replace('(','')
		data_dict={}
		status_flag=re.findall(r'.{1}', data[7])
		#状态码切片
		
		data_dict['INPUT_Voltage']=[data[0],'V']
		data_dict['INPUT_Fault_Vlotage']=[data[1],'V']
		data_dict['OUTPUT_Vlotage']=[data[2],'V']
		data_dict['OUTPUT_Load']=[data[3],'%']
		data_dict['OUTPUT_Frequency']=[data[4],'Hz']
		data_dict['Battery_Vlotage']=[float(data[5])*96,'V']
		data_dict['UPS_Temperature']=[data[6],'degress C']
		
		data_dict['Speaker_Status']=[status_flag[7],'#']
		data_dict['Shutdown_Active_Status']=[status_flag[6],'#']
		data_dict['Test_in_Progress']=[status_flag[5],'#']
		data_dict['UPS_Type']=[status_flag[4],'#']
		data_dict['UPS_System_Failed']=[status_flag[3],'#']
		data_dict['Bypass_Status']=[status_flag[2],'#']
		data_dict['Battery_Low_Alarm']=[status_flag[1],'#']
		data_dict['Input_Power_Status']=[status_flag[0],'#']

	except Exception as err:
		raise err
	else:
		return data_dict

def print_json(value_list):
	try:

		data={
			"prtg": {
			"result": [
				{
				"Channel": "Run Result",
				"CustomUnit": "#",
				"Mode":"Absolute",
				"Float":1,
				"Value":"0"
				}
			]
			}
			}
		
		for i in value_list:
			data_channels={
				"Channel": i,
				"CustomUnit": value_list[i][1],
				"Mode":"Absolute",
				"Float":2,
				"Value":value_list[i][0]
			}
			data['prtg']['result'].append(data_channels)
		print (json.dumps(data, sort_keys=True, indent=2))
	except Exception as err:
		raise err

def main():

	try:
		serial_session=serial.Serial(port=PORT, baudrate=2400, bytesize=8, parity='N', stopbits=1, xonxoff=0)
		#定义串口连接参数
		serial_data=get_serial_data(serial_session)
		data=sort_serial_data(serial_data)
		print_json(data)
	except Exception as err:
		data={
          "prtg": {
           "error": 1,
           "text": str(err)
          }
         }
		print (json.dumps(data, sort_keys=True, indent=2))


if __name__ == "__main__":
	main()

输出符合PRTG监控系统格式的json结构体数据

{
  "prtg": {
    "result": [
      {
        "Channel": "Run Result",
        "CustomUnit": "#",
        "Float": 1,
        "Mode": "Absolute",
        "Value": "0"
      },
      {
        "Channel": "INPUT_Voltage",
        "CustomUnit": "V",
        "Float": 2,
        "Mode": "Absolute",
        "Value": "234.9"
      },
      {
        "Channel": "INPUT_Fault_Vlotage",
        "CustomUnit": "V",
        "Float": 2,
        "Mode": "Absolute",
        "Value": "234.9"
      },
      {
        "Channel": "OUTPUT_Vlotage",
        "CustomUnit": "V",
        "Float": 2,
        "Mode": "Absolute",
        "Value": "220.0"
      },
      {
        "Channel": "OUTPUT_Load",
        "CustomUnit": "%",
        "Float": 2,
        "Mode": "Absolute",
        "Value": "006"
      },
      {
        "Channel": "OUTPUT_Frequency",
        "CustomUnit": "Hz",
        "Float": 2,
        "Mode": "Absolute",
        "Value": "50.0"
      },
      {
        "Channel": "Battery_Vlotage",
        "CustomUnit": "V",
        "Float": 2,
        "Mode": "Absolute",
        "Value": 219.84
      },
      {
        "Channel": "UPS_Temperature",
        "CustomUnit": "degress C",
        "Float": 2,
        "Mode": "Absolute",
        "Value": "28.9"
      },
      {
        "Channel": "Speaker_Status",
        "CustomUnit": "#",
        "Float": 2,
        "Mode": "Absolute",
        "Value": "1"
      },
      {
        "Channel": "Shutdown_Active_Status",
        "CustomUnit": "#",
        "Float": 2,
        "Mode": "Absolute",
        "Value": "0"
      },
      {
        "Channel": "Test_in_Progress",
        "CustomUnit": "#",
        "Float": 2,
        "Mode": "Absolute",
        "Value": "0"
      },
      {
        "Channel": "UPS_Type",
        "CustomUnit": "#",
        "Float": 2,
        "Mode": "Absolute",
        "Value": "0"
      },
      {
        "Channel": "UPS_System_Failed",
        "CustomUnit": "#",
        "Float": 2,
        "Mode": "Absolute",
        "Value": "0"
      },
      {
        "Channel": "Bypass_Status",
        "CustomUnit": "#",
        "Float": 2,
        "Mode": "Absolute",
        "Value": "0"
      },
      {
        "Channel": "Battery_Low_Alarm",
        "CustomUnit": "#",
        "Float": 2,
        "Mode": "Absolute",
        "Value": "0"
      },
      {
        "Channel": "Input_Power_Status",
        "CustomUnit": "#",
        "Float": 2,
        "Mode": "Absolute",
        "Value": "0"
      }
    ]
  }
}
Program ended with exit code: 0

三、PRTG中的值查询功能

上面的例子中,我们将UPS的状态码传递给上层监控系统。但是监控系统如何知道状态码为0和为1时候,代表什么意思呢?

{
        "Channel": "Input_Power_Status",
        "CustomUnit": "#",
        "Float": 2,
        "Mode": "Absolute",
        "Value": "0"
      }

例如这个通道的数据中,Value字段表示市电输入状态,为0时表示输入正常,为1时表示市电输入中断。
这时候,我们需要用PRTG的值查询功能,将不同的数值定义成不同的状态。

1、新建值查询定义文件

在PRTG核心服务器的C:\Program Files (x86)\PRTG Network Monitor\lookups\custom目录下,我们新建一个Santak_UPS_RS232_Alarm.ovl文件,并用记事本打开进行编辑,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
  <ValueLookup id="Santak_UPS_RS232_Alarm" desiredValue="1" undefinedState="Warning">
    <Lookups>
      <SingleInt state="OK" value="0">
        Run_Status_OK
      </SingleInt>
      <SingleInt state="Error" value="1">
        Run_Status_Error
      </SingleInt>
    </Lookups>
  </ValueLookup>

我们定义:
value=0的时候,文本显示"Run_Status_OK",状态为正常(OK)。
value=1的时候,文本显示"Run_Status_Error",状态为停机(Error)。
value等于其他值的时候,undefinedState=“Warning”,状态为告警(Warning)。

2、加载定义文件

在PRTG控制台中,进入系统->管理工具->点击加载查询和文件列表
在这里插入图片描述

3、编辑通道,调用定义文件

点击通道右侧的齿轮图标,打开通道编辑
在这里插入图片描述
在编辑通道窗口内,在“值查询”下拉菜单中选中刚才我们新建的定义文件。
在这里插入图片描述
然后,这个通道的值就不再是简单的0和1了。而是变成了带有文本描述和告警状态定义的通道状态。
在这里插入图片描述

搞定!

往期回顾:
【逗老师带你学IT】PRTG监控通过Python+Modbus RTU获取温湿度传感器数据
【逗老师带你学IT】PRTG监控通过Python+Modbus TCP获取温湿度传感器数据
【逗老师带你学IT】职场数据中心异地出口容灾,H3C的IP上一跳保持技术
【逗老师带你学IT】阿里云监控报警回调+转发企业微信+转发SnmpTrap+PRTG
【逗老师带你学IT】HUAWEI华为防火墙自动化运维Python ssh管理网络设备
【逗老师带你学IT】PRTG获取HUAWEI FusionServer iBMC传感器状态
【逗老师带你学IT】PRTG自定义脚本ssh登录网络设备获负载均衡链路状态
【逗老师带你学IT】Django+IIS+Python构建微软AD域控API管理中心
【逗老师带你学IT】通过企业微信推送AD域密码即将到期提醒
【逗老师带你学IT】AD域控 Dsquery 查询命令实例汇总
【逗老师带你学IT】Google Admin服务账号+API管理G suit内所有网域用户
【逗老师带你学IT】PRTG监控系统通过企业微信推送图文混排告警消息
【逗老师带你学IT】PRTG HTTP API获取指定传感器流量图表图片
【逗老师带你学IT】PRTG监控系统合并多个传感器通道数据
【逗老师带你学IT】PRTG监控系统通过企业微信推送告警消息
【逗老师带你学IT】PRTG监控系统配合树莓派采集企业内部无线网络质量
【逗老师带你学IT】vMware ESXi 6.7合并第三方硬件驱动
【逗老师带你学IT】Kiwi Syslog Server安装和配置教程
【逗老师带你学IT】Kiwi Syslog Web Access与Active Directory集成认证
【逗老师带你学IT】vMware ESXi 6.7合并第三方硬件驱动
【逗老师带你学IT】Windows Server Network Policy Service(NPS)记账与审计
【逗老师带你学IT】Windows Server NPS服务构建基于AD域控的radius认证
【逗老师带你学IT】AD域控和freeradius集成认证环境,PAP,MSCHAPV2
【逗老师带你学IT】深信服SSL远程接入与深信服行为审计同步登陆用户信息

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逗老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值