I'm no code bunny. Here's the current working code: powerfail.py
#!/usr/bin/env python
'''
This program monitors the status of the PWRGOOD signal from the UPS circuit.
PWRGOOD is normally high. If it goes low then the input power has failed.
This program senses when PWRGOOD falls and then samples it once/second. An
accumulator counts up by 1 if power is bad and counts down by 3 if power is good.
This accounts for the difference in supercap charge current vs. Zero-W load current.
If the accumulator exceeds 20 then it signals the UPS to disconnect the
power by asserting SHUTDOWN. This will still cause a shutdown condition even when
the power is cycling on and off. After it commits the hardware to shutdown it also
sends a shutdown command to the Linux system. The UPS will hold power up for 20 seconds
after it receives the SHUTDOWN signal, which allows the Pi to enter a shutdown state
prior to power removal.
'''
import RPi.GPIO as GPIO
import os, time, sys
import logging
import logging.handlers
#import traceback
# global variables
COUNT_UP = 1
COUNT_DOWN = 3 # this relates to the ratio of charger current divided by load current
COUNT_MAX = 20 * COUNT_UP # how many seconds of power loss before the Pi shuts down
# set the GPIO pin assignments
PWRGOOD = 23
SHUTDOWN = 24
#--logger definitions
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO) # Could be e.g. "TRACE", "ERROR", "" or "WARNING"
handler = logging.handlers.RotatingFileHandler("/var/log/bud_logs/ups_mon.log", maxBytes=1000, backupCount=4) # save 4 logs
formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
class MyLogger():
'''
A class that can be used to capture stdout and sterr to put it in the log
'''
def __init__(self, level, logger):
'''Needs a logger and a logger level.'''
self.logger = logger
self.level = level
def write(self, message):
# Only log if there is a message (not just a new line)
if message.rstrip() != "":
self.logger.log(self.level, message.rstrip())
def flush(self):
pass # do nothing -- just to handle the attribute for now
# configure the GPIO ports:
GPIO.setmode(GPIO.BCM)
# PWRGOOD is an input with weak pullup
GPIO.setup(PWRGOOD, GPIO.IN, pull_up_down = GPIO.PUD_UP)
def powerfail_detect(count_max, count_up, count_down):
# if the count exceeds 20 the system will shutdown.
count = 0
pwr_flip = False
while (count < count_max):
time.sleep(1)
if (GPIO.input(PWRGOOD)):
if (pwr_flip):
logger.info("Power is good")
count -= count_down
pwr_flip = False
if (count <= 0):
logger.info("Returning to normal status")
return
else:
if (not pwr_flip):
logger.info("Power has failed")
count += count_up
pwr_flip = True
logger.info("powerfail iminent...shutting down the Pi!\n")
GPIO.setup(SHUTDOWN,GPIO.OUT)
GPIO.output(SHUTDOWN,0)
time.sleep(0.1)
GPIO.output(SHUTDOWN,1)
time.sleep(0.1)
os.system("sudo shutdown -h now")
GPIO.cleanup()
return
# If this script is installed as a Daemon, set this flag to True:
DAEMON = True # when run as daemon, pipe all console information to the log file
# --Replace stdout and stderr with logging to file so we can run it as a daemon
# and still see what is going on
if DAEMON :
sys.stdout = MyLogger(logging.INFO, logger)
sys.stderr = MyLogger(logging.ERROR, logger)
try:
logger.info("Enabled")
while True:
time.sleep(1)
if (not GPIO.input(PWRGOOD)):
powerfail_detect(count_max=COUNT_MAX, count_up=COUNT_UP, count_down=COUNT_DOWN)
except:
GPIO.cleanup() # clean up GPIO on CTRL+C exit
Note that the log file is located in /var/log/bud_logs/ups_mon.log. It will rotate the log after 1000bytes and keep the latest four logs. You can change all of that by altering the handler params.
The above script is located in /home/pi/programs/ and runs as a service using systemd on either the Jessie or Stretch version of Raspbian. You can locate it anywhere as long as the service knows the path. The powerfail.service file below is located in /lib/systemd/system/
[Unit] Description=Powerfail Service Before=multi-user.target [Service] Type=idle ExecStart=/usr/bin/python -u /home/pi/programs/powerfail.py Restart=on-failure RestartSec=10 TimeoutSec=10 [Install] WantedBy=multi-user.target
Note that the ExecStart line must have full pathnames to both python and the program. In order to activate this service you need to type the following in a terminal shell:
sudo systemctl daemon-reload
sudo systemctl enable powerfail
sudo systemctl start powerfail
Now the service is running and will automatically run the powerfail.py code upon boot. To check the status of the service simply type:
systemctl status powerfail
Here's an example of the log file output:
2018-11-13 11:38:01,870 INFO powerfail iminent...shutting down the Pi! 2018-11-13 11:38:30,161 INFO Enabled 2018-11-13 11:41:25,759 INFO Power has failed 2018-11-13 11:41:29,767 INFO Power is good 2018-11-13 11:41:30,772 INFO Returning to normal status
I will provide a copy of powerfail.py in the files section of this project.
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.