#!/bin/bash
###########################################################################################
# Thinkpad battery gauge reset using TP_SMAPI module                                      #
#                                                                                         #
# Script to perform battery gauge on a Thinkpad laptop in Linux (tested on Ubuntu) but it #
# should (hopefully) work on other distros as well, as long as you use the same kernel    #
# module (tp-smapi)                                                                       #
#                                                                                         #
# More info on the kernel module and usage can be found on the excellent thinkwiki site:  #
# http://www.thinkwiki.org/wiki/Tp_smapi                                                  #
# Please follow the instructions on the website in order to install the module            #
#                                                                                         #
# Since I'm not using Windows anymore (apart from Office in a VM within Linux), I do not  #
# have access to the Thinkpad utility that performs the battery gauge. Whenever I was     #
# using the battery gauge utility I was gaining some max energy battery capacity, so I    #
# thought of implementing this to retain the gain without having to switch to native      #
# Windows :) Hope this works for you as well                                              #
#                                                                                         #
# Nikolas Ioannou, 2011                                                                   #
#                                                                                         #
# Sample usage:                                                                           #
# 1.Change ownership of driver files so that we don't need to run this as root,           #
#   not a good idea to run stuff as root unless absolutely necessary:                     #
# $sudo chown USER /sys/devices/platform/smapi/BAT0/*                                     #
#                                                                                         #
# 2. Simply run the script:                                                               #
# $./thinkpad_tp_smapi_battery_reset.sh                                                   #
#                                                                                         #
# 3. Go get a cup of coffee and do something else, this will usually take some time:)     #
#                                                                                         #
# This script has been tested on the following hardware/software configuration:           #
# Lenovo Thinkpad X200s with a 9-cell Panasonic battery                                   #
# running:                                                                                #
# Ubuntu Linux 10.10 64 bit, kernel 2.6.35-30                                             #
# ThinkPad hardware/firmware access modules debian package: tp-smapi-dkms, version 0.40-7 #
# For comments/suggestions/BUGS please contact me at: nikolas.ioannou@ed.ac.uk            #
###########################################################################################

# Global variables pointing to the relevant file descriptors used
# by the ts_smapi driver
DRIVER_PATH="/sys/devices/platform/smapi/BAT0"
DISCHARGE_FD="$DRIVER_PATH/force_discharge"
CAPACITY_FD="$DRIVER_PATH/remaining_capacity"
PERCENT_FD="$DRIVER_PATH/remaining_percent"
POWER_FD="$DRIVER_PATH/power_now"
REMAININGCHARINGTIME_FD="$DRIVER_PATH/remaining_charging_time"

###########################################################################################
#                                                                                         #
# The following code snippet that does the error handling has been                        #
# leveraged from the (brilliant) stackoverflow site. One cannot do things                 #
# without some googling help                                                              #
#                                                                                         #
###########################################################################################
#This function is used to cleanly exit any script. It does this displaying a
# given error message, and exiting with an error code.
function error_exit {
    echo
    echo "$@"
    # important! disable forced discharge before exiting!
    if [ -w $DISCHARGE_FD ]; then
        echo 0 > $DISCHARGE_FD
    fi
    exit 1
}
#Trap the killer signals so that we can exit with a good message.
trap "error_exit 'Received signal SIGHUP'" SIGHUP
trap "error_exit 'Received signal SIGINT'" SIGINT
trap "error_exit 'Received signal SIGTERM'" SIGTERM

#Alias the function so that it will print a message with the following format:
#prog-name(@line#): message
#We have to explicitly allow aliases, we do this because they make calling the
#function much easier (see example).
shopt -s expand_aliases
alias die='error_exit "Error ${0}(@`echo $(( $LINENO - 1 ))`):"'

###########################################################################################

# first read the current values
# check that we have permission to write to the interface
if [ ! -w $DISCHARGE_FD ]; then
    die "no permission to write to $DISCHARGE_FD"
    #echo "file is what opt tells it is"
fi

# ok, we verified that we are able to write to the interface
# entering main loop

while [ 1 ]; do
    # 1. check for some prerequisite conditions
    ISDECHARGING=`cat $DISCHARGE_FD`
    if [ $ISDECHARGING -ne 0 ]; then
        echo 0 > $DISCHARGE_FD
        ISDECHARGING=`cat $DISCHARGE_FD`
    fi

    # if failed to set force_discharge to 0 (i.e. disable forced discharge)
    # then exit, but do so nicely :)
    if [ $ISDECHARGING -ne 0 ]; then
        die "Error in setting DESCHARGE flag to ZERO!"
    fi

    # check if on ac power, no reason to proceed if not
    # and notify user
    POWERNOW=`cat $POWER_FD`
    if [ $POWERNOW -lt 0 ]; then
        die "Cannot perform battery reset if not on AC power! Connect AC adapter and try again"
    fi

    # 2. Charge battery to full
    echo "Step one, about to fully charge the battery"
    CURPERCENT=`cat $PERCENT_FD`
    PREVPERCENT=$CURPERCENT
    echo "Battery currently at $CURPERCENT% capacity"
    REMCHARGINGTIME=`cat $REMAININGCHARINGTIME_FD`
    while [ $REMCHARGINGTIME != "charged" ]; do
        # first sleep for a little, give the battery a chance to charge :P
        sleep 5
        REMCHARGINGTIME=`cat $REMAININGCHARINGTIME_FD`
        if [ $REMCHARGINGTIME = "not_charging" ]; then
            die "Battery currently not charging, did you disconnect the AC power?"
        fi

        CURPERCENT=`cat $PERCENT_FD`
        if [ $CURPERCENT -ne $PREVPERCENT ]; then
            echo "Battery currently at $CURPERCENT% capacity, charging"
            PREVPERCENT=$CURPERCENT
        fi
    done
    # now battery is fully charged, Hurray!

    # 3. Discharge battery
    # first thing to do is set the force_discarge flag to 1
    echo 1 > $DISCHARGE_FD
    ISDECHARGING=`cat $DISCHARGE_FD`
    if [ $ISDECHARGING -ne 1 ]; then
        die "Error in setting DESCHARGE flag to ONE!"
    fi

    CURPERCENT=`cat $PERCENT_FD`
    PREVPERCENT=$CURPERCENT
    echo "Battery currently at $CURPERCENT% capacity"
    CURCAPACITY=`cat $CAPACITY_FD`
    PREVCAPACITY=$CURCAPACITY
    while [ $CURCAPACITY -gt 0 && $CURCAPACITY -le $PREVCAPACITY ]; do
        # save the previous capacity value
        PREVCAPACITY=$CURCAPACITY
        # sleep for a little while, give the battery a chance to discharge :P
        sleep 5
        # check weather the AC adapter has been disconected for some reason,
        # exit if it did
        POWERNOW=`cat $POWER_FD`
        if [ $POWERNOW -lt 0 ]; then
            die "Battery discharge requires the AC adapter to be connected! Connect AC adapter and try again"
        fi
        CURCAPACITY=`cat $CAPACITY_FD`

        CURPERCENT=`cat $PERCENT_FD`
        if [ $CURPERCENT -ne $PREVPERCENT ]; then
            echo "Battery currently at $CURPERCENT% capacity, discharging"
            PREVPERCENT=$CURPERCENT
        fi
    done
    # now the battery should be completely discarged! Hurray!

    # 4. Recharge the battery now, just like step 2
    # first thing to do is set the force_discarge flag to 0, i.e. disable forced discharge
    echo 0 > $DISCHARGE_FD
    ISDECHARGING=`cat $DISCHARGE_FD`
    if [ $ISDECHARGING -ne 0 ]; then
        die "Error in setting DESCHARGE flag to ZERO!"
    fi
    CURPERCENT=`cat $PERCENT_FD`
    PREVPERCENT=$CURPERCENT
    echo "Battery currently at $CURPERCENT% capacity"
    REMCHARGINGTIME=`cat $REMAININGCHARINGTIME_FD`
    while [ $REMCHARGINGTIME != "charged" ]; do
        # first sleep for a little, give the battery a chance to charge :P
        sleep 5
        REMCHARGINGTIME=`cat $REMAININGCHARINGTIME_FD`
        if [ $REMCHARGINGTIME = "not_charging" ]; then
            die "Battery currently not charging, did you disconnect the AC power?"
        fi

        CURPERCENT=`cat $PERCENT_FD`
        if [ $CURPERCENT -ne $PREVPERCENT ]; then
            echo "Battery currently at $CURPERCENT% capacity, charging"
            PREVPERCENT=$CURPERCENT
        fi
    done
    # Our work here is done! Hurray!
    # Notify the user for our success and exit; do so peacefully
    echo "Battery gauge reset successfully completed, hope you get some extra mileage out of your battery"
    echo "Exiting.."
    exit 0
done


