-
Notifications
You must be signed in to change notification settings - Fork 0
/
pi_power.py
298 lines (205 loc) · 9.02 KB
/
pi_power.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
#!/usr/bin/env python
# pi_power.py
# Robert Jones 2016 [email protected]
# 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 = 13
# 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.00
battery_max_voltage = 3.75
# 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 = 5
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