|
#!/bin/sh |
|
# Pixel 3a battery Balancer |
|
|
|
# --- Hardware Paths --- |
|
CHARGER="/sys/class/power_supply/pm660-charger/current_max" |
|
BAT_FLOW="/sys/class/power_supply/qcom-battery/current_now" |
|
PM660_FLOW="/sys/class/power_supply/pm660-charger/current_now" |
|
BAT_CAP="/sys/class/power_supply/qcom-battery/capacity" |
|
PWR_CONN="/sys/class/power_supply/pm660-charger/online" |
|
|
|
# --- Logic Configuration --- |
|
UPPER_CAP=65 |
|
LOWER_CAP=45 |
|
SAFE_DRAIN_CUR=100000 # 100mA |
|
SAFE_MAX_CUR=1000000 # 1A |
|
STEP=25000 # Hardware resolution (25mA) |
|
HALF_STEP=$((STEP / 2)) # For rounding to nearest step |
|
DEAD_ZONE=$HALF_STEP # 12.5mA - this is becasue we only have 25mA resolution, so we want to avoid constant toggling |
|
GAIN_NUM=1 # Damping: apply GAIN_NUM/GAIN_DEN of correction per cycle |
|
GAIN_DEN=2 # 1/2 = 50% gain — converge gradually |
|
ERROR_ACCUM=0 # Accumulate fractional error lost to quantization |
|
DEBUG_LOG=1 |
|
|
|
SAMPLES=10 |
|
SAMPLE_RATE="0.2" |
|
REST_PERIOD=5 |
|
IDLE_PERIOD=20 |
|
|
|
echo "Balancer pixel3a Started. capacity: $(cat $BAT_CAP)% Target: ${LOWER_CAP}%-${UPPER_CAP}%" |
|
|
|
while true; do |
|
# Check for active power connection |
|
IS_PLUGGED=$(cat $PWR_CONN 2>/dev/null || echo 0) |
|
|
|
if [ "$IS_PLUGGED" -eq 0 ]; then |
|
[ "$DEBUG_LOG" -eq 1 ] && echo "$(date +%T) [IDLE] No power detected. Sleeping ${IDLE_PERIOD}s." |
|
sleep "$IDLE_PERIOD" |
|
continue |
|
fi |
|
|
|
|
|
# Battery current sampling |
|
ITER=0; SUM_FLOW=0 |
|
while [ $ITER -lt $SAMPLES ]; do |
|
SUM_FLOW=$((SUM_FLOW + $(cat $BAT_FLOW))) |
|
ITER=$((ITER + 1)) |
|
sleep $SAMPLE_RATE |
|
done |
|
AVG_FLOW=$((SUM_FLOW / SAMPLES)) |
|
CUR_LIMIT=$(cat $CHARGER) |
|
|
|
# Control Logic |
|
CAP=$(cat $BAT_CAP) |
|
if [ "$CAP" -ge "$UPPER_CAP" ] && [ "$CUR_LIMIT" -gt "$SAFE_DRAIN_CUR" ]; then |
|
echo $SAFE_DRAIN_CUR > $CHARGER |
|
echo "$(date +%T) [EVENT] Capacity $CAP%. Dropping to floor $((SAFE_DRAIN_CUR / 1000))mA." |
|
continue |
|
fi |
|
|
|
if [ "$CAP" -le "$LOWER_CAP" ] && [ "$CUR_LIMIT" -lt "$SAFE_MAX_CUR" ]; then |
|
echo $SAFE_MAX_CUR > $CHARGER |
|
echo "$(date +%T) [EVENT] Capacity $CAP%. Recharging at $((SAFE_MAX_CUR / 1000))mA." |
|
continue |
|
fi |
|
|
|
# If Capacity is in the "Happy Zone" (45-70), we balance. |
|
if [ "$CAP" -gt "$LOWER_CAP" ] && [ "$CAP" -lt "$UPPER_CAP" ]; then |
|
|
|
ERROR=$(( -1 * AVG_FLOW )) |
|
# If error magnitude is greater than dead zone, adjust. Otherwise, we're close enough |
|
if [ ${ERROR#-} -gt "$DEAD_ZONE" ]; then |
|
|
|
# Combine with accumulated fractional error from previous rounding losses |
|
TOTAL_ERROR=$(( ERROR + ERROR_ACCUM )) |
|
|
|
# Apply damped correction |
|
CORRECTION=$(( (TOTAL_ERROR * GAIN_NUM) / GAIN_DEN )) |
|
RAW_TARGET=$(( CUR_LIMIT + CORRECTION )) |
|
NEW_LIMIT=$(( ((RAW_TARGET + HALF_STEP) / STEP) * STEP )) |
|
|
|
# Clamp to safe hardware boundaries |
|
[ $NEW_LIMIT -gt "$SAFE_MAX_CUR" ] && NEW_LIMIT=$SAFE_MAX_CUR |
|
[ $NEW_LIMIT -lt "$SAFE_DRAIN_CUR" ] && NEW_LIMIT=$SAFE_DRAIN_CUR |
|
|
|
if [ "$NEW_LIMIT" -ne "$CUR_LIMIT" ]; then |
|
echo $NEW_LIMIT > $CHARGER |
|
[ "$DEBUG_LOG" -eq 1 ] && echo "$(date +%T) [PI_UPDATE] Flow: $((AVG_FLOW / 1000))mA. Correction: $((CORRECTION / 1000))mA. Setting Limit: $((NEW_LIMIT / 1000))mA (Calc: $((RAW_TARGET / 1000))mA)" |
|
fi |
|
|
|
# Calculate how much error was actually corrected and save the remainder |
|
CORRECTION_APPLIED=$(( NEW_LIMIT - CUR_LIMIT )) |
|
ERROR_CORRECTED=$(( (CORRECTION_APPLIED * GAIN_DEN) / GAIN_NUM )) |
|
ERROR_ACCUM=$(( TOTAL_ERROR - ERROR_CORRECTED )) |
|
else |
|
[ "$DEBUG_LOG" -eq 1 ] && echo "$(date +%T) [STABLE] Flow: $((AVG_FLOW / 1000))mA is within Deadzone." |
|
fi |
|
fi |
|
|
|
sleep "$REST_PERIOD" |
|
done |