From 02739735389bbaddf1ed91f68ff64329ad7ce52a Mon Sep 17 00:00:00 2001 From: Torsten Kurbad Date: Thu, 7 Sep 2017 16:27:39 +0200 Subject: [PATCH] Initial commit --- pyupsmon.py | 210 +++++++++++++++++++++++++++++++++++++++++++ pyupsmon.yml.example | 36 ++++++++ 2 files changed, 246 insertions(+) create mode 100644 pyupsmon.py create mode 100644 pyupsmon.yml.example diff --git a/pyupsmon.py b/pyupsmon.py new file mode 100644 index 0000000..3a8403e --- /dev/null +++ b/pyupsmon.py @@ -0,0 +1,210 @@ +#!/usr/bin/python3 +# +# Depends on pyyaml! + +import shlex +import subprocess +import sys +import yaml + +from serial import Serial +from threading import Timer +from time import sleep + + +class ConvertUPS(Serial): + """ Class that implements monitoring for Wöhrle Convert-[123]000 UPS + devices connected via RS232. + Has to be initialized like serial.Serial(). + """ + + # Class variables + _isOnBattery = False # Is UPS running on battery right now? + _powerToggled = False # Did power state (battery / AC) toggle recently? + + + def __init__(self, configfile = '/etc/pyupsmon.yml', port = None, + baudrate = None, interval = None, shutdownTimeout = None, + shutdownCommand = None, debug = None, *args, **kwargs): + """ Read config file and/or set default config values. """ + self.serialPort = port + self.serialBaud = baudrate + self.interval = interval + self.shutdownTimeout = shutdownTimeout + self.shutdownCommand = shutdownCommand + self.debug = debug + + try: + with open(configfile, 'r') as ymlfile: + cfg = yaml.load(ymlfile) + + if cfg.get('serial') is not None: + if self.serialPort is None: + self.serialPort = cfg['serial'].get('port', '/dev/ttyS0') + if self.serialBaud is None: + self.serialBaud = int(cfg['serial'].get('baudrate', 2400)) + if cfg.get('daemon') is not None: + if self.interval is None: + self.interval = float(cfg['daemon'].get('interval', 1.0)) + if self.debug is None: + self.debug = bool(cfg['daemon'].get('debug', False)) + if cfg.get('shutdown') is not None: + if self.shutdownTimeout is None: + self.shutdownTimeout = float(cfg['shutdown'].get('timeout', 3600)) + if self.shutdownCommand is None: + self.shutdownCommand = shlex.split(cfg['shutdown'].get('command', '/sbin/shutdown -h now')) + except OSError: + if debug: + print('Could not read config file %s.' % configfile) + + if self.serialPort is None: + self.serialPort = '/dev/ttyS0' + if self.serialBaud is None: + self.serialBaud = 2400 + if self.interval is None: + self.interval = 1.0 + if self.debug is None: + self.debug = False + if self.shutdownTimeout is None: + self.shutdownTimeout = 3600.0 + if self.shutdownCommand is None: + self.shutdownCommand = shlex.split('/sbin/shutdown -h now') + + if self.debug: + print('Configuration:') + print('\tSerial Port:\t\t%s' % self.serialPort) + print('\tBaud Rate:\t\t%d' % self.serialBaud) + print('\tQuery Interval:\t\t%.2f s' % self.interval) + print('\tDebugging:\t\t%s' % self.debug) + print('\tShutdown Timeout:\t%.2f' % self.shutdownTimeout) + print('\tShutdown Command:\t%s' % self.shutdownCommand) + + super(ConvertUPS, self).__init__(port = self.serialPort, + baudrate = self.serialBaud, *args, **kwargs) + + + def upsInit(self): + """ Start communicating with the UPS. """ + if not self.name: + if self.debug: + raise NameError('Please, set the serial communication parameters first.') + if self.debug: + print('Successfully connected to serial port %s' % (ups.name)) + print('Sending initialization sequence (you should hear a short beep).') + + self.write(b'CT\rDQ1\r') + sleep(5.0) + self.write(b'Rt\r') + sleep(3.0) + + self.write(b'Q1\r') + sleep(1.0) + + readbuffer = b'' + data = self.read(1) + while b'\r' not in data: + readbuffer += data + data = self.read(1) + + if readbuffer.strip().startswith(b'('): + if self.debug: + print('Initialization successful.') + print('Initial values returned:', readbuffer.strip()) + return True + return + + + def upsRead(self): + """ Read the current status of the UPS. """ + ups.write(b'Q1\r') + readbuffer = b'' + data = ups.read(1) + + while b'\r' not in data: + readbuffer += data + data = ups.read(1) + if self.debug: + print(readbuffer.strip()) + return readbuffer.strip() + + + def isOnBattery(self): + """ Check whether UPS is on Battery. """ + status = self.upsRead() + if status.startswith(b'(000.0 '): + if not self._isOnBattery: + self._isOnBattery = True + self._powerToggled = True + if self.debug: + print('UPS is on battery.') + else: + if self._isOnBattery: + self._isOnBattery = False + self._powerToggled = True + if self.debug: + print('UPS is on AC.') + return self._isOnBattery + + + def powerToggled(self): + """ Check if a power toggle (AC -> Battery or vice versa) + occured and reset the flag. + """ + onBattery = self.isOnBattery() + if self._powerToggled: + self._powerToggled = False + if self.debug: + print('Power toggle has occured recently.') + if onBattery: + return(True, True) + else: + return(True, False) + if self.debug: + print('No power toggle has occured recently.') + if onBattery: + return (False, True) + return (False, False) + + + def doShutdown(self): + """ Execute self.shutdownCommand. """ + subprocess.call(self.shutdownCommand) + + + def startShutdownTimer(self): + """ Start a timer that executes self.shutdownCommand as soon as + self.shutdownTimeout has expired. + If self.shutdownTimeout == 0, the timer is disabled. + """ + if self.shutdownTimeout > 0: + self.timer = Timer(self.shutdownTimeout, self.doShutdown) + self.timer.start() + + + def cancelShutdownTimer(self): + """ Cancel a previously started shutdown timer. """ + if self.timer is not None: + self.timer.cancel() + self.timer = None + + +if __name__ == "__main__": + #try: + ups = ConvertUPS() + #except: + # print('Error: Could not open UPS serial connection.') + # print('\tCheck the config file!') + # sys.exit(1) + + if not ups.upsInit(): + print('Error: Could not initialize UPS.') + sys.exit(1) + + while True: + (toggled, onBatt) = ups.powerToggled() + if toggled: + if onBatt: + ups.startShutdownTimer() + if not onBatt: + ups.cancelShutdownTimer() + sleep(ups.interval) diff --git a/pyupsmon.yml.example b/pyupsmon.yml.example new file mode 100644 index 0000000..b41583e --- /dev/null +++ b/pyupsmon.yml.example @@ -0,0 +1,36 @@ +# Serial port parameters +serial: + # Serial port the UPS is connected to + # + # Default: /dev/ttyS0 + #port: /dev/ttyS0 + + # Baud rate to set for the serial port + # + # Default: 2400 + #baudrate: 2400 + +# Daemon settings +daemon: + # UPS query interval (in seconds) + # + # Default: 1.0 + #interval: 2 + + # Enable debug output? + # + # Default: 0 + #debug: 1 + +# Shutdown settings +shutdown: + # Shutdown the monitoring host after this many seconds + # on battery (0 to disable) + # + # Default: 3600.0 + #timeout: 180.0 + + # Shutdown command to execute after timeout expired + # + # Default: /sbin/shutdown -h now + #command: /sbin/shutdown -h now