#!/usr/bin/env python # pi_power.py # Robert Jones 2016 jones@craic.com # The code for reading the MCP3008 analog to digital convertor (readadc) was # written by Limor "Ladyada" Fried for Adafruit Industries, (c) 2015 # This code is released into the public domain # Works with an Adafruit PowerBoost 1000C LiPo battery charger # Writes the fraction of battery remaining as well as the current power source to the file /home/pi/.pi_power # format: <float 0.00 - 1.00>,<string [battery|usb]> # 0.75,battery # 1.00,usb import time import os import argparse import RPi.GPIO as GPIO # read SPI data from MCP3008 chip, 8 possible adc's (0 thru 7) def readadc(adcnum, clockpin, mosipin, misopin, cspin): if ((adcnum > 7) or (adcnum < 0)): return -1 GPIO.output(cspin, True) GPIO.output(clockpin, False) # start clock low GPIO.output(cspin, False) # bring CS low commandout = adcnum commandout |= 0x18 # start bit + single-ended bit commandout <<= 3 # we only need to send 5 bits here for i in range(5): if (commandout & 0x80): GPIO.output(mosipin, True) else: GPIO.output(mosipin, False) commandout <<= 1 GPIO.output(clockpin, True) GPIO.output(clockpin, False) adcout = 0 # read in one empty bit, one null bit and 10 ADC bits for i in range(12): GPIO.output(clockpin, True) GPIO.output(clockpin, False) adcout <<= 1 if (GPIO.input(misopin)): adcout |= 0x1 GPIO.output(cspin, True) adcout >>= 1 # first bit is 'null' so drop it return adcout # Calculate the output of a voltage divider # voltage_divider layout is: # Vin ---[ R1 ]---[ R2 ]---GND # | # Vout # # Vout = R2 / (R1 + R2) * Vin # e.g. if R1 = 6800 and R2 = 10000 and Vin is 5.2V then Vout is 3.095 # def voltage_divider(r1, r2, vin): vout = vin * (r2 / (r1 + r2)) return vout # Set up a trigger to shutdown the system when the power button is pressed # define a setup routine and the actual shutdown method # The shutdown code is based on that in https://github.com/NeonHorizon/lipopi def user_shutdown_setup(shutdown_pin): # setup the pin to check the shutdown switch - use the internal pull down resistor GPIO.setup(shutdown_pin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) # create a trigger for the shutdown switch GPIO.add_event_detect(shutdown_pin, GPIO.RISING, callback=user_shutdown, bouncetime=1000) # User has pressed shutdown button - initiate a clean shutdown def user_shutdown(channel): global safe_mode shutdown_delay = 10 # seconds # in Safe Mode, wait 2 mins before actually shutting down if(safe_mode): cmd = "sudo wall 'System shutting down in 2 minutes - SAFE MODE'" os.system(cmd) time.sleep(120) cmd = "sudo wall 'System shutting down in %d seconds'" % shutdown_delay os.system(cmd) time.sleep(shutdown_delay) # Log message is added to /var/log/messages os.system("sudo logger -t 'pi_power' '** User initiated shut down **'") GPIO.cleanup() os.system("sudo shutdown now") # Shutdown system because of low battery def low_battery_shutdown(): global safe_mode shutdown_delay = 30 # seconds # in Safe Mode, wait 2 mins before actually shutting down if(safe_mode): cmd = "sudo wall 'System shutting down in 2 minutes - SAFE MODE'" os.system(cmd) time.sleep(120) cmd = "sudo wall 'System shutting down in %d seconds'" % shutdown_delay os.system(cmd) time.sleep(shutdown_delay) # Log message is added to /var/log/messages os.system("sudo logger -t 'pi_power' '** Low Battery - shutting down now **'") GPIO.cleanup() os.system("sudo shutdown now") # MAIN ----------------------- GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) # Command Line Arguments # --log write time, voltage, etc to a log file # --debug write time, voltage, etc to STDOUT parser = argparse.ArgumentParser(description='Pi Power - Monitor battery status on RasPi projects powered via Adafruit PowerBoost 1000C') parser.add_argument('-d', '--debug', action='store_true') parser.add_argument('-l', '--log', action='store_true') parser.add_argument('-s', '--safe', action='store_true') args = parser.parse_args() safe_mode = False if(args.safe): safe_mode = True # Setup the GPIO pin to use with the use shutdown button user_shutdown_pin = 26 user_shutdown_setup(user_shutdown_pin) # Setup the connection to the ADC # specify the Raspberry Pi GPIO pins to be used to connect to the SPI interface on the MCP3008 ADC SPICLK = 18 SPIMISO = 23 SPIMOSI = 24 SPICS = 25 # set up the SPI interface pins GPIO.setup(SPIMOSI, GPIO.OUT) GPIO.setup(SPIMISO, GPIO.IN) GPIO.setup(SPICLK, GPIO.OUT) GPIO.setup(SPICS, GPIO.OUT) # Vbat to adc #0, Vusb connected to #1 v_bat_adc_pin = 0 v_usb_adc_pin = 1 # Voltage divider drops the PowerBoost voltage from around 5V to under 3.3V which is the limit for the Pi voltage_divider_r1 = 6800.0 voltage_divider_r2 = 10000.0 # Define the min and max voltage ranges for the inputs usb_min_voltage = 0.0 usb_max_voltage = 5.2 gpio_min_voltage = 0.0 gpio_max_voltage = 3.3 # LiPo battery voltage range - actual range is 3.7V to 4.2V # But in practice the measured range is reduced as Vbat always drops from 4.2 to around 4.05 when the # USB cable is removed - so this is the effective range: battery_min_voltage = 3.75 battery_max_voltage = 4.05 # this is the effective max voltage, prior to the divider, that the ADC can register adc_conversion_factor = (gpio_max_voltage / voltage_divider(voltage_divider_r1, voltage_divider_r2, usb_max_voltage)) * usb_max_voltage pi_power_status_path = '/home/pi/.pi_power_status' pi_power_log_path = '/home/pi/pi_power_log.csv' # initialize an empty log file if(args.log): with open(pi_power_log_path, "w") as f: f.write('Time,Vbat,Vusb,Frac,Source\n') # Take a measurement every poll_interval * seconds * - default 60 poll_interval = 60 power_source = '' power_source_previous = '' fraction_battery = 1.0 # Define the minimum battery level at which shutdown is triggered fraction_battery_min = 0.075 if(args.debug): print 'Time Vbat Vusb Frac Source' elapsed_time = 0 msg = '' while True: # read the analog pins on the ACD (range 0-1023) and convert to 0.0-1.0 frac_v_bat = round(readadc(v_bat_adc_pin, SPICLK, SPIMOSI, SPIMISO, SPICS)) / 1023.0 frac_v_usb = round(readadc(v_usb_adc_pin, SPICLK, SPIMOSI, SPIMISO, SPICS)) / 1023.0 # Calculate the true voltage v_bat = frac_v_bat * adc_conversion_factor v_usb = frac_v_usb * adc_conversion_factor fraction_battery = (v_bat - battery_min_voltage) / (battery_max_voltage - battery_min_voltage) if fraction_battery > 1.0: fraction_battery = 1.0 elif fraction_battery < 0.0: fraction_battery = 0.0 # is the USB cable connected ? Vusb is either 0.0 or around 5.2V if v_usb > 1.0: power_source = 'usb' else: power_source = 'battery' if power_source == 'usb' and power_source_previous == 'battery': print '** USB cable connected' elif power_source == 'battery' and power_source_previous == 'usb': print '** USB cable disconnected' power_source_previous = power_source msg = '' # If battery is too low then shutdown if fraction_battery < fraction_battery_min: msg = 'Low Battery - shutdown now' if(args.debug): print "** LOW BATTERY - shutting down........" # shutdown after writing to the log file if(args.debug): print '{0:6d} {1:.3f} {2:.3f} {3:.3f} {4:s} {5:s}'.format(elapsed_time, v_bat, v_usb, fraction_battery, power_source, msg) # Open log file, write one line and close # This handles the case where the battery is allowed to drain completely and # shutdown in which case the file may be corrupted if(args.log): with open(pi_power_log_path, "a") as f: f.write('{0:d},{1:.3f},{2:.3f},{3:.3f},{4:s},{5:s}\n'.format(elapsed_time, v_bat, v_usb, fraction_battery, power_source, msg)) # Write the .pi_power status file - used by pi_power_leds.py with open(pi_power_status_path, "w") as f: f.write('{0:.3f},{1:s}\n'.format(fraction_battery,power_source)) # Low battery shutdown - specify the time delay in seconds if fraction_battery < fraction_battery_min: low_battery_shutdown() # sleep poll_interval seconds between updates time.sleep(poll_interval) elapsed_time += poll_interval