Skip to content

Commit

Permalink
updates
Browse files Browse the repository at this point in the history
  • Loading branch information
slowrunner committed Apr 22, 2024
1 parent ffbf556 commit 1bcdc8a
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 16 deletions.
1 change: 1 addition & 0 deletions config/daveData.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"lastDocking": "---- Docking 0 completed at 0.0 v after 0.0 h playtime", "chargeCycles": 0, "lastDismount": "---- Dismount 0 at 0.0 v after 0.0 h recharge", "dockingState": 0, "chargingState": 0, "lastDismountTime": "YYYY-MM-DD HH:MM:SS", "lastRechargeDuration": "0.0", "newBatteryDate": "2021-06-21", "newBatteryAtDocking": "0", "newBatteryAtLifeHours": "0.0", "newBatteryDesc": "Original GoPiGo3/TalentCell 12v 3000mAh", "lastDockingTime": "YYYY-MM-DD HH:MM:SS", "lastPlaytimeDuration": "0.0"}
105 changes: 90 additions & 15 deletions plib/battery.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
# PURPOSE: Central facts and methods for the ModRobotics/TalentCell YB1203000 Li-Ion Battery Pack
# 12.6v to 9v 3A "3000mAh" Battery Pack
#
# Update:
# 2024-04: Added Battery class which uses INA219 current and voltage sensor

import sys
sys.path.insert(1,"/home/pi/GoPi5Go/plib/")
Expand All @@ -14,28 +16,28 @@
import numpy as np
import time
import statistics
import daveDataJson
import ina219


CHARGING_ADJUST_V = 0.2 # When charging the GoPiGo3 reading is closer to the actual battery voltage
REV_PROTECT_DIODE = 0.76 # The GoPiGo3 has a reverse polarity protection diode drop of 0.6v to 0.8v (n=2)
REV_PROTECT_DIODE = 0.7 # The GoPiGo3 has a reverse polarity protection diode drop of 0.6v to 0.8v (n=2)
# This value results in average readings vs battery voltage error of +/- 0.03
SAFETY_SHUTDOWN_vBatt = 9.75 # Battery Discharge Protection Circuit allows down to 8.2v or so (9.75 leaves 5-15m reserve)
SAFETY_SHUTDOWN_vBatt = 9.75 # Battery Discharge Protection Circuit allows down to 8.2v or so (9.75 leaves 5-12m reserve)
SAFETY_SHUTDOWN_vReading = SAFETY_SHUTDOWN_vBatt - REV_PROTECT_DIODE # 8.5v EasyGoPiGo3.volt() reading
WARNING_LOW_vBatt = 10.25 # Give (~15 minutes) Advance Warning before safety shutdown

# 2024-3-24 Data
# VOLTAGE_POINTS = \
# [8.21, 9.489, 9.643, 9.754, 9.934, 9.969, 10.011, 10.08, 10.157, 10.243, 10.303, 10.39, 10.474, 10.594, 10.731, 10.885, 10.945, 11.108, 11.348, 11.44, 11.605, 12.4]
WARNING_LOW_vBatt = 10.0 # Give (~15 minutes) Advance Warning before safety shutdown

# egpg.volt() Data points
# 2024-4-2 Data 4h19m 12.5v to 8.91v at cutoff - To Cutoff: 9.84v = 5m, 10.15v = 5% 13m, 10.29v = 10% 26m, 10.32v = 12% 20m
VOLTAGE_POINTS = \
[8.91, 10.15, 10.29, 10.42, 10.56, 10.64, 10.69, 10.75, 10.81, 10.87, 10.9, 11.0, 11.14, 11.31, 11.42, 11.56, 11.68, 11.87, 12.0, 12.13, 12.36, 12.5]
[8.5, 10.15, 10.29, 10.42, 10.56, 10.64, 10.69, 10.75, 10.81, 10.87, 10.9, 11.0, 11.14, 11.31, 11.42, 11.56, 11.68, 11.87, 12.0, 12.13, 12.36, 12.5]
BATTERY_REMAINING = \
[0.0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.50, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 0.99, 1.00]
[0.0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.50, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 0.99, 1.00]

def pctRemaining_from_vBatt(vBatt):
FULL_CHARGE = 1.0
PROTECTION_CUTOFF = 8.21
pctRemaining = np.interp(vBatt,VOLTAGE_POINTS,BATTERY_REMAINING, left=FULL_CHARGE, right=PROTECTION_CUTOFF)
PROTECTION_CUTOFF = 0.0
pctRemaining = np.interp(vBatt,VOLTAGE_POINTS,BATTERY_REMAINING, right=FULL_CHARGE, left=PROTECTION_CUTOFF)
return pctRemaining

def vBatt_vReading(egpg):
Expand All @@ -46,7 +48,8 @@ def vBatt_vReading(egpg):
str_to_log="Exception "+type(e).__name__+": "+str(e)+" vReading:{:.2f}".format(vReading)+" continuing"
print(str_to_log)
lifeLog.logger.info(str_to_log)
if (vReading > 11.7):
chargingState = daveDataJson.getData('chargingState')
if (chargingState == "charging"):
# charging
vBatt = vReading + REV_PROTECT_DIODE - CHARGING_ADJUST_V
# print("charging")
Expand All @@ -57,7 +60,7 @@ def vBatt_vReading(egpg):
def voltages_string(egpg):
vBatt, vReading = vBatt_vReading(egpg)
pctRemaining = pctRemaining_from_vBatt(vBatt)*100.0
return "Current Battery {:.1f}v {:.0f}% (Reading {:.2f}v)".format(vBatt,pctRemaining,vReading)
return "Current Battery {:.1f}v {:.1f}% (Reading {:.2f}v)".format(vBatt,pctRemaining,vReading)

def too_low(egpg):
vBatt, _ = vBatt_vReading(egpg)
Expand All @@ -83,11 +86,83 @@ def pctRemaining(egpg):
return(pctRemaining_from_vBatt(aveBatteryV(egpg)))


class Battery:
"""Class containing TalentCell YB1203000 parameters and methods"""
ina = None # INA219 class instance
SHUNT_OHMS = 0.1
MAX_EXPECTED_AMPS = 2.0

def __init__(self):
self.ina = ina219.INA219(self.SHUNT_OHMS, self.MAX_EXPECTED_AMPS,log_level=None)
self.ina.configure(self.ina.RANGE_16V, bus_adc=self.ina.ADC_128SAMP,shunt_adc=self.ina.ADC_128SAMP)

def ave_voltage(self):
vlist = []
for i in range(3):
vBatt = self.ina.voltage()
vlist += [vBatt]
time.sleep(0.01) # cannot be faster than 0.005
return statistics.mean(vlist)

def ave_current(self):
clist = []
for i in range(3):
cBatt = self.ina.current()
clist += [cBatt]
time.sleep(0.01) # cannot be faster than 0.005
return statistics.mean(clist)

def ave_power(self):
plist = []
for i in range(3):
pBatt = self.ina.power()
plist += [pBatt]
time.sleep(0.01) # cannot be faster than 0.005
return statistics.mean(plist)/1000.0

def pctRemaining(self):
# ina219.voltage() Data points
# 2024-4-2 Data 4h19m 12.5v to 8.91v at cutoff - To Cutoff: 9.84v = 5m, 10.15v = 5% 13m, 10.29v = 10% 26m, 10.32v = 12% 20m
VOLTAGE_POINTS = \
[8.5, 9.74, 9.88, 10.04, 10.16, 10.22, 10.25, 10.26, 10.38, 10.43, 10.46, 11.53, 11.59, 10.69, 10.79, 10.89, 11.00, 11.17, 11.29, 11.44, 11.53, 11.55]
BATTERY_REMAINING = \
[0.0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.50, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 0.99, 1.00]

FULL_CHARGE = 1.0
PROTECTION_CUTOFF = 0.0
vBatt = self.ave_voltage()
pctRemaining = np.interp(vBatt,VOLTAGE_POINTS,BATTERY_REMAINING, right=FULL_CHARGE, left=PROTECTION_CUTOFF) * 100
return pctRemaining

def too_low(self):
vBatt = self.ave_voltage()
return vBatt < SAFETY_SHUTDOWN_vBatt

def on_last_leg(self):
vBatt = self.ave_voltage()
return vBatt < WARNING_LOW_vBatt


def status_string(self):
vBatt = self.ave_voltage()
cBatt = self.ave_current()
rBatt = self.pctRemaining()
pBatt = self.ave_power()
return "Current Battery {:.2f}v {:.1f}% Load: {:.0f}mA {:.1f}W".format(vBatt,rBatt,cBatt,pBatt)






def testMain():
egpg = EasyGoPiGo3(use_mutex=True, noinit=True)
print(voltages_string(egpg))
print("Battery Remaining: {:.0f}% at 10.15v".format(pctRemaining_from_vBatt(10.15)*100 ))
print("Battery Percent Remaining: {:.0f}%".format(pctRemaining(egpg)*100 ))
print("Battery Remaining: {:.0f}% at 10.0v".format(pctRemaining_from_vBatt(10.0)*100 ))
print("Battery Remaining: {:.0f}% at 9.75v".format(pctRemaining_from_vBatt(9.75)*100 ))
print("Battery Percent Remaining now: {:.0f}%".format(pctRemaining(egpg)*100 ))

batt = Battery()
print(batt.status_string())

if __name__ == '__main__': testMain()
164 changes: 164 additions & 0 deletions plib/daveDataJson.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#!/usr/bin/env python3

# file: daveDataJson.py
#
# Serialize data values to /home/pi/GoPi5Go/daveData.json
# (running ./daveDataJson.py will create the file)
#
# Methods:
# saveData(dataname, datavalue, logit=False) # adds datanaem:datavalue to rosbotData.json file
# getData(dataname=None) # either returns dictionary with all values, or just value of passed name
# delData(dataname) # delete item from rosbotData.json
# printData() # prints contents of rosbotData.json
#

"""
Example from Carl:
lastDocking : ---- Docking 4573 completed at 8.1 v after 2.6 h playtime
chargeCycles : 4573
lastDismount : ---- Dismount 4573 at 10.9 v after 2.5 h recharge
dockingState : 1
chargingState : 1
chargeConditioning : 0
lastDismountTime : 2024-04-15 06:47:03
lastRechargeDuration : 2.5
newBatterySetDate : 2023-03-21
newBatterySetAtDocking : 3652
newBatterySetAtLifeHours : 36810.9
newBatterySetDesc : 8x Eneloop White 2000 mAh NiMH AA cells
lastDockingTime : 2024-04-15 04:15:21
lastPlaytimeDuration : 2.6
"""
import sys
sys.path.insert(1,'/home/pi/GoPi5Go/plib')

import json
import threading
# import runLog
DATA_FILE = '/home/pi/GoPi5Go/daveData.json'

DataLock = threading.Lock() # with DataLock: any operation to make syncronous

def saveData(dataname, datavalue, logit=False):


# print("-- saveData({},{}) called".format(dataname, datavalue))
with DataLock: # prevents two different saveData() at same time
lData = {}
# print("got lock")
try:

lData = getData() # lock assures no one changes this till we are done
if lData == None:
lData = {}
# print(" Data:", lData)
lData[dataname] = datavalue
# print(" lData:",lData)

with open(DATA_FILE, 'w') as outfile:
json.dump( lData, outfile )
# print(" Data.json updated")
if logit: runLog.logger.info("** Data '{}' = {} updated **".format(dataname, datavalue))
except:
# print(" saveData failed")
return False

return True

def delData(dataname):
# print("-- delData({}) called".format(dataname))

with DataLock:
lData = {}
try:

lData = getData()
if lData == None:
lData = ()
# print(" Data:", lData)
if dataname in lData:
del lData[dataname]
# print(" lData:", lData)

with open(DATA_FILE, 'w') as outfile:
json.dump( lData, outfile )
# print(" Data.json updated")
# else: print(" {} not found in Data".format(dataname))
except:
# print(" delData{} failed".dataname)
return False

return True


def getData(dataname=None):


# print("-- getData({}) called".format(dataname))

try:
with open(DATA_FILE, 'r') as infile:
lData = json.load(infile)
if (dataname == None):
return lData
else:
return lData[dataname]
except:
# print(" getData() exception")
return None

def printData():
print("daveData.json contents:")
lData = getData()
if lData != None:
for i in lData:
print(" ",i," : ",lData[i])
else:
print("daveData.json contains no data")

def main():

print("** Starting main()")

printData()

lData = getData()
print(" daveData: ",lData)

chargeCycles = 1

if (saveData('chargeCycles', chargeCycles) == True):
print(' Saved chargeCycles: {}'.format(chargeCycles))
else:
print(" saveData('chargeCycles') failed")

lData = getData()
print(" daveData: ",lData)


chargeCycles = int(getData('chargeCycles'))
chargeCycles += 1

if (saveData('chargeCycles', chargeCycles) == True):
print(' Saved chargeCycles: {}'.format(chargeCycles))
else:
print(" saveData('chargeCycles') failed")


if 'chargeCycles' in lData:
print("removing chargeCycles from daveData.json")
delData('chargeCycles')

if (saveData('nothing',"not important" ) == True):
print(' Saved nothing: {}'.format("not important"))
else:
print(" saveData('nothing') failed")

lData = getData()
print(" Data: ",lData)


if __name__ == "__main__":
main()

20 changes: 20 additions & 0 deletions systests/docking/test_daveData.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env python3

import sys
sys.path.insert(1,'/home/pi/GoPi5Go/plib')
import daveDataJson



try:
chargeCycles = int(daveDataJson.getData('chargeCycles'))
chargeCycles += 1
except:
chargeCycles = 0


if (daveDataJson.saveData('chargeCycles', chargeCycles) == True):
print(' Saved chargeCycles: {}'.format(chargeCycles))
else:
print(" saveData('chargeCycles') failed")

13 changes: 13 additions & 0 deletions utils/print_daveDataJson.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env python3

import sys
sys.path.insert(1,"/home/pi/GoPi5Go/plib")

import daveDataJson

# print("daveDataJson contents:")
# lcarlData = carlDataJson.getCarlData()
# for i in lcarlData:
# print(" ",i," : ",lcarlData[i])

daveDataJson.printData()
12 changes: 11 additions & 1 deletion utils/totallife.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
# Sessions: "- boot -" ( "\- boot \-" search string )
# Safety shutdowns: "SAFETY SHUTDOWN"

declare -i newBatteryAtCycle=416

echo "(Cleaning life.log first)"
/home/pi/GoPi5Go/plib/cleanlifelog.py
echo " "
Expand All @@ -19,11 +21,19 @@ ofn='/home/pi/GoPi5Go/logs/odometer.log'
totalAwake=`(awk -F'execution:' '{sum+=$2}END{print sum;}' $fn)`
totalNaps=`(awk -F'nap for' '{sum+=$2}END{print sum;}' $fn)`
totalLife=`(echo "scale=1; ($totalAwake + $totalNaps)" | bc)`
lastDockingStr=`(grep "h playtime" $fn | tail -1)`
totalDockings=`(awk -F"Docking " '{sub(/ .*/,"",$2);print $2}' <<< $lastDockingStr)`
currentBattCycles=`(echo "scale=1; $totalDockings - $newBatteryAtCycle" | bc)`

echo "*** GoPi5Go Dave TOTAL LIFE STATISTICS ***"
echo "Total Awake: " $totalAwake " hrs"
echo "Total Naps: " $totalNaps " hrs"
echo "Total Life: " $totalLife " hrs (since Mar 17, 2024)"
echo "Playtimes (Undocked-Docked):" `(grep -c " Docking: success " $fn)`
echo "GoPi5Go-Dave Playtimes (Undocked-Docked):" `(grep -c ": success" $fn)`
echo "Total Dockings: " $totalDockings
echo "New Batteries At Cycle:" $newBatteryAtCycle
echo "Battery At Cycle: " $currentBattCycles

# last5playtimes=`(grep " hrs playtime " $fn | tail -5 | awk -F" after " '{sum+=$2}END{print sum;}' )`
# last5avePlaytime=`(echo "scale=1; $last5playtimes / 5" | bc)`
# echo "Average playtime (last five)" $last5avePlaytime "hrs "
Expand Down

0 comments on commit 1bcdc8a

Please sign in to comment.