Skip to content

Commit 7173661

Browse files
committed
complete docstrings and beta release prep
1 parent 8c401bc commit 7173661

4 files changed

Lines changed: 166 additions & 29 deletions

File tree

README.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22
A set of utilities to monitor and react to the status of a supported UPS
33

44
## Getting Started
5-
For any of the utilities to function, the config.py files must be customized using config.py.template as
6-
a template. This file contains information required for the daemon utility to function.
5+
A set of configuration parameters are used when the config.py file is not provided. These can be viewed with
6+
the command *ups-daemon --list_params*. Alternatives to these defaults can be specified in the config.py file
7+
using the config.py.template file as a template.
78

89
Also, a UPS list must be specified in the config.json file using config.json.template as a template. This file
9-
contains details about each UPS that make snmp communication possible. The utility required snmp v2c in order
10-
to communicate with the network accessible UPSs.
10+
contains details about each UPS that make snmp communication possible. The utility requires snmp v2c in order
11+
to communicate with the network accessible UPSs. As a result, you must configure your target Network attached
12+
UPS devices to use SNMPv2 with a know Private Community String.
13+
14+
The ups-utils rely on the command *snmpget* which is part of the snmp package that must be installed.
1115

1216
## ups-daemon
1317
With no options specified, the utility will give the current status of the UPS configured with *daemon = true*
@@ -42,14 +46,18 @@ output a table of the current status. The *--long* option will include addition
4246
informational parameters. By default, unresponsive UPSs will not be displayed, but the
4347
*--show_unresponsive* can be used to force their display.
4448

49+
## New in the Release - [v0.9.0](https://github.com/Ricks-Lab/ups-utils/releases/tag/v0.9.0)
50+
* Initial Beta Release - Please feedback your question/issues in the project's issues. Thanks!
51+
4552
## Under Development
4653
The utility currently supports:
4754
* APC UPS with AP9630 NMC
4855
* EATON UPS with PowerWalker NMC
4956

5057
It monitors the specified UPS using snmp v2c. I have not implemented the ability to listen to snmp traps
5158
yet, as I still have some research to do. If you have different UPS and would like to extend the dictionary
52-
in this [code](https://github.com/Ricks-Lab/ups-utils/blob/master/UPSmodules/UPSmodule.py) to support it, feel free to make a pull request.
59+
in this [code](https://github.com/Ricks-Lab/ups-utils/blob/master/UPSmodules/UPSmodule.py) to support it, feel
60+
free to make a pull request.
5361

5462
## Reference Material
5563
* [apc-ups-snmp](https://github.com/phillipsnick/apc-ups-snmp)

UPSmodules/UPSmodule.py

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -628,27 +628,44 @@ def read_all_ups_list_items(self, command_list, errups=True):
628628
results[ups_name] = self.read_ups_list_items(command_list)
629629
return results
630630

631-
def read_ups_list_items(self, command_list):
632-
results = {'display_name': self.ups_name(),
633-
'name': self.ups_name(),
634-
'uuid': self.ups_uuid(),
635-
'ups_IP': self.ups_ip(),
636-
'ups_type': self.ups_type()}
631+
def read_ups_list_items(self, command_list, tups=None):
632+
""" Read the specified list of monitor mib commands for all UPSs.
633+
:param command_list: A list of mib commands to be read from the active UPS
634+
:type command_list: list
635+
:param tups: The target ups dictionary from list or None.
636+
:type tups: dict
637+
:return: dict of results from the reading of all commands target UPS.
638+
"""
639+
if not tups:
640+
tups = self.active_ups
641+
results = {'display_name': self.ups_name(tups=tups),
642+
'name': self.ups_name(tups=tups),
643+
'uuid': self.ups_uuid(tups=tups),
644+
'ups_IP': self.ups_ip(tups=tups),
645+
'ups_type': self.ups_type(tups=tups)}
637646
for cmd in command_list:
638-
results[cmd] = self.send_snmp_command(cmd)
647+
results[cmd] = self.send_snmp_command(cmd, tups=tups)
639648
return results
640649

641-
def send_snmp_command(self, command_name, target_ups=None, display=False):
642-
if not target_ups:
643-
target_ups = self.active_ups
644-
if not self.is_responsive(target_ups):
650+
def send_snmp_command(self, command_name, tups=None, display=False):
651+
""" Read the specified mib commands results for specified UPS or active UPS if not specified.
652+
:param command_name: A command to be read from the target UPS
653+
:type command_name: str
654+
:param tups: The target ups dictionary from list or None.
655+
:type tups: dict
656+
:param display: If true the results will be printed
657+
:type display: bool
658+
:return: The results from the read
659+
"""
660+
if not tups:
661+
tups = self.active_ups
662+
if not self.is_responsive(tups):
645663
return 'Invalid UPS'
646-
snmp_mib_commands = self.get_mib_commands(target_ups)
664+
snmp_mib_commands = self.get_mib_commands(tups)
647665
if command_name not in snmp_mib_commands:
648666
return 'No data'
649667
cmd_mib = snmp_mib_commands[command_name]['iso']
650-
cmd_str = 'snmpget -v2c -c {} {} {}'.format(target_ups['snmp_community'],
651-
target_ups['ups_IP'], cmd_mib)
668+
cmd_str = 'snmpget -v2c -c {} {} {}'.format(tups['snmp_community'], tups['ups_IP'], cmd_mib)
652669
try:
653670
snmp_output = subprocess.check_output(shlex.split(cmd_str), shell=False,
654671
stderr=subprocess.DEVNULL).decode().split('\n')
@@ -667,7 +684,7 @@ def send_snmp_command(self, command_name, target_ups=None, display=False):
667684
if snmp_mib_commands[command_name]['decode']:
668685
if value in snmp_mib_commands[command_name]['decode'].keys():
669686
value = snmp_mib_commands[command_name]['decode'][value]
670-
if target_ups['ups_type'] == 'eaton-pw':
687+
if tups['ups_type'] == 'eaton-pw':
671688
if command_name == 'mib_output_voltage' or command_name == 'mib_output_frequency':
672689
value = int(value) / 10.0
673690
elif command_name == 'mib_output_current':
@@ -676,11 +693,11 @@ def send_snmp_command(self, command_name, target_ups=None, display=False):
676693
value = int(value) / 10.0
677694
elif command_name == 'mib_system_temperature':
678695
value = int(value) / 10.0
679-
if command_name == 'mib_system_status' and target_ups['ups_type'] == 'apc-ap9630':
696+
if command_name == 'mib_system_status' and tups['ups_type'] == 'apc-ap9630':
680697
value = self.bit_str_decoder(value, self.decoders['apc_system_status'])
681698
if command_name == 'mib_time_on_battery' or command_name == 'mib_battery_runtime_remain':
682699
# Create a minute, string tuple
683-
if target_ups['ups_type'] == 'eaton-pw':
700+
if tups['ups_type'] == 'eaton-pw':
684701
# Process time for eaton-pw
685702
if command_name == 'mib_time_on_battery':
686703
# Measured in seconds.
@@ -704,10 +721,11 @@ def send_snmp_command(self, command_name, target_ups=None, display=False):
704721
@staticmethod
705722
def bit_str_decoder(value, decode_key):
706723
""" Bit string decoder
707-
708-
:param value: A string representing a bit encoded set of flags
709-
:param decode_key: A list representing the meaning of a 1 for each bit field
710-
:returns: A string of concatenated bit decode strings
724+
:param value: A string representing a bit encoded set of flags
725+
:type value: str
726+
:param decode_key: A list representing the meaning of a 1 for each bit field
727+
:type decode_key: list
728+
:return: A string of concatenated bit decode strings
711729
"""
712730
value_str = ''
713731
for index, bit_value in enumerate(value):
@@ -721,6 +739,11 @@ def bit_str_decoder(value, decode_key):
721739
return value_str
722740

723741
def print_snmp_commands(self, tups=None):
742+
""" Print all supported mib commands for the target UPS, which is the active UPS when not specified.
743+
:param tups: The target ups dictionary from list or None.
744+
:type tups: dict
745+
:return: None
746+
"""
724747
if not tups:
725748
tups = self.active_ups
726749
for k, v in self.get_mib_commands(tups).items():
@@ -733,6 +756,9 @@ def print_snmp_commands(self, tups=None):
733756

734757
# Set parameters required for daemon mode.
735758
def set_daemon_parameters(self):
759+
""" Set all daemon parameters based on defaults in env.ut_const and the config.py file.
760+
:return: None
761+
"""
736762
if env.ut_const.ERROR_config:
737763
print('Error in config.py file. Using defaults')
738764
return
@@ -818,11 +844,17 @@ def set_daemon_parameters(self):
818844
print('Invalid threshold_battery_capacity_warn in config.py. Using default.')
819845

820846
def print_daemon_parameters(self):
847+
""" Print all daemon parameters.
848+
:return: None
849+
"""
821850
print('Daemon parameters:')
822851
for k, v in self.daemon_params.items():
823852
print(' {}: {}'.format(k, v))
824853

825854
def shutdown(self):
855+
""" Execute the shutdown script as defined in the daemon parameters.
856+
:return: None
857+
"""
826858
if not self.daemon_params['shutdown_script']:
827859
print('No shutdown script defined')
828860
return
@@ -838,6 +870,9 @@ def shutdown(self):
838870
file=sys.stderr)
839871

840872
def cancel_shutdown(self):
873+
""" Execute the cancel shutdown script as defined in the daemon parameters.
874+
:return: None
875+
"""
841876
if not self.daemon_params['cancel_shutdown_script']:
842877
print('No cancel shutdown script defined')
843878
return
@@ -853,6 +888,9 @@ def cancel_shutdown(self):
853888
self.daemon_params['cancel_shutdown_script']), file=sys.stderr)
854889

855890
def resume(self):
891+
""" Execute the resume script as defined in the daemon parameters.
892+
:return: None
893+
"""
856894
if not self.daemon_params['resume_script']:
857895
print('No resume script defined')
858896
return
@@ -868,6 +906,9 @@ def resume(self):
868906
file=sys.stderr)
869907

870908
def suspend(self):
909+
""" Execute the suspend script as defined in the daemon parameters.
910+
:return: None
911+
"""
871912
if not self.daemon_params['suspend_script']:
872913
print('No suspend script defined')
873914
return
@@ -885,6 +926,9 @@ def suspend(self):
885926

886927

887928
def about():
929+
""" Display details about this module.
930+
:return: None
931+
"""
888932
# About me
889933
print(__doc__)
890934
print("Author: ", __author__)

UPSmodules/env.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#!/usr/bin/env python3
22
"""env.py - sets environment for ups-utils and establishes global variables
33
4-
54
Copyright (C) 2019 RueiKe
65
76
This program is free software: you can redistribute it and/or modify
@@ -72,11 +71,19 @@ def __init__(self):
7271

7372
@staticmethod
7473
def now(ltz=False):
74+
""" Get current time
75+
:param ltz: Set to True to use local time zone.
76+
:type ltz: bool
77+
:return: Returns current time as datetime object
78+
"""
7579
if ltz:
7680
return datetime.now()
7781
return datetime.utcnow()
7882

7983
def check_env(self):
84+
""" Check the user's environment for compatibility.
85+
:return: Returns an integer indicating env error code: 0 for passes
86+
"""
8087
# Check python version
8188
required_pversion = [3, 6]
8289
(python_major, python_minor, python_patch) = platform.python_version_tuple()
@@ -117,6 +124,9 @@ def check_env(self):
117124

118125

119126
def about():
127+
""" Display details about this module.
128+
:return: None
129+
"""
120130
print(__doc__)
121131
print('Author: ', __author__)
122132
print('Copyright: ', __copyright__)

ups-monitor

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,17 @@ from gi.repository import GLib, Gtk, Gdk
5959

6060
class MonitorWindow(Gtk.Window):
6161
def __init__(self, ups, header_items, command_items, devices):
62+
""" Initialize the main UPS monitor window.
63+
:param ups: The main ups module object
64+
:type ups: UPSsnmp
65+
:param header_items: A list of non-mib parameters
66+
:type header_items: list
67+
:param command_items: A list of mib parameter results
68+
:type command_items: list
69+
:param devices: A dictionary of Gtk components and values
70+
:type devices: dict
71+
:return: None
72+
"""
6273
self.quit = False
6374

6475
Gtk.Window.__init__(self, title='ups-monitor')
@@ -161,10 +172,26 @@ class MonitorWindow(Gtk.Window):
161172
row += 1
162173

163174
def set_quit(self, _arg2, _arg3):
175+
""" Function called when quit monitor is executed. Sets flag to end update loop.
176+
:param _arg2: Ignored
177+
:param _arg3: Ignored
178+
:return:
179+
"""
164180
self.quit = True
165181

166182

167183
def updateData(ups, header_list, command_list, devices):
184+
""" Function that updates data in MonitorWindow with call to read data from ups.
185+
:param ups: The main ups module object
186+
:type ups: UPSsnmp
187+
:param header_list: A list of non-mib parameters
188+
:type header_list: list
189+
:param command_list: A list of mib parameter results
190+
:type command_list: list
191+
:param devices: A dictionary of Gtk components and values
192+
:type devices: dict
193+
:return: None
194+
"""
168195
irw = 20
169196
ups_data = ups.read_all_ups_list_items(command_list, errups=env.ut_const.show_unresponsive)
170197
if env.ut_const.LOG:
@@ -224,6 +251,23 @@ def updateData(ups, header_list, command_list, devices):
224251

225252

226253
def refresh(refreshtime, updateData, ups, header_list, command_list, devices, umonitor):
254+
""" Function that continuously updates the Gtk monitor window.
255+
:param refreshtime: Delay time in seconds between monitor display refreshes
256+
:type refreshtime: int
257+
:param updateData: Function name that does the actual refresh of the monitor table.
258+
:type updateData: FunctionType
259+
:param ups: The main ups module object
260+
:type ups: UPSsnmp
261+
:param header_list: A list of non-mib parameters
262+
:type header_list: list
263+
:param command_list: A list of mib parameter results
264+
:type command_list: list
265+
:param devices: A dictionary of Gtk components and values
266+
:type devices: dict
267+
:param umonitor: The main Gtk monitor window.
268+
:type umonitor: MonitorWindow
269+
:return: None
270+
"""
227271
while True:
228272
if umonitor.quit:
229273
print('Quitting...')
@@ -239,12 +283,23 @@ def refresh(refreshtime, updateData, ups, header_list, command_list, devices, um
239283

240284

241285
def print_monitor_table(ups, header_list, cmd_list, ups_data):
286+
""" Print the monitor table in format optimized for terminal window.
287+
:param ups: The main ups module object
288+
:type ups: UPSsnmp
289+
:param header_list: A list of non-mib parameters
290+
:type header_list: list
291+
:param cmd_list: A list of mib parameter results
292+
:type cmd_list: list
293+
:param ups_data: A dictionary of results for items listed in header and command lists
294+
:type ups_data: dict
295+
:return:
296+
"""
242297
num_ups = ups.get_num_ups_tuple()
243298
if num_ups[0] < 1:
244299
return -1
245300

246-
hrw = 29
247-
irw = 24
301+
hrw = 29 # Row header item width
302+
irw = 24 # Row data item width
248303

249304
print('┌', '─'.ljust(hrw, '─'), sep='', end='')
250305
for _, _ in ups_data.items():
@@ -294,6 +349,17 @@ def print_monitor_table(ups, header_list, cmd_list, ups_data):
294349

295350

296351
def print_log(fileptr, header_list, command_list, ups_data):
352+
""" Print the logfile data line.
353+
:param fileptr: The logfile fileptr
354+
:type fileptr: file
355+
:param header_list: A list of non-mib parameters
356+
:type header_list: list
357+
:param command_list: A list of mib parameter results
358+
:type command_list: list
359+
:param ups_data: A dictionary of results for items listed in header and command lists
360+
:type ups_data: dict
361+
:return:
362+
"""
297363
time_str = (str(env.ut_const.now(ltz=True).strftime('%c')).strip())
298364
for k, v in ups_data.items():
299365
print('{}'.format(time_str), file=fileptr, end='')
@@ -305,6 +371,15 @@ def print_log(fileptr, header_list, command_list, ups_data):
305371

306372

307373
def print_log_header(fileptr, header_list, command_list):
374+
""" Print the logfile header line.
375+
:param fileptr: The logfile fileptr
376+
:type fileptr: file
377+
:param header_list: A list of non-mib parameters
378+
:type header_list: list
379+
:param command_list: A list of mib parameter results
380+
:type command_list: list
381+
:return:
382+
"""
308383
print('time', file=fileptr, end='')
309384
for item in header_list:
310385
print('|{}'.format(item), file=fileptr, end='')

0 commit comments

Comments
 (0)