Battery charge limit without root but with RaspberryPi "Smart Charger" hack

With FP4 (and probably other models) it is not possible to limit charging of the battery to a certain percentage. Limiting the charge to certain percentage increases the lifespan of your battery, other manufactures allow you to set such a charge limit, the sustainable Fairphone company apparently has decided such a feature is not necessary and when you want to do it with an app, you need to root your phone, which comes with other problems, which should be considered before taking action. (E.g. banking apps tend to not work on rooted phones)

Here I share how I implemented a “smart charger” with a RaspberryPi, a common cheap singleboard computer. You can set a charging limit for your phone without rooting it. However, it does require you to enable ADB USB debugging, which could cause security issues and it is not intended use of this feature. So it would be better, easier and more efficient for Fairphone to implement it in their OS.

DISCLAIMER: This is not an officially supported system and I do not take any responsibility for you breaking your device or for this to work properly, safe and secure. Replicate at your own risk.

Drawbacks:

  • The RaspberryPi continually draws idle current wasting energy
  • Slow charging (500mA / 2.5W)
  • Enabling ADB USB debugging on the phone could cause security issues

Description

So in very short how does it work?

The RPi is configured to connect to your phone, reads the battery percentage, and when your set threshold is reached (e.g. 80%) it turns off the USB port for a while. When the port is turned on again, it looks again for the percentage and decides on either to charge or turn off the port again.

More detailed description:

It queries your phone through ADB to get the phone battery percentage. This is done in a systemd service, which is triggered automatically as soon as an USB device is connected over an udev rule. This then checks for an android device with adb, and terminates if not found. If found, it checks the percentage regularly and deactivates the port when your threshold (80% set by standard) is reached.

Between 23:00 and 05:00 it will deactivate the USB port until 05:00, otherwise it will deactivate if for an hour until activating it again and repeat polling of the battery level.

Install

What you need is:

  • RaspberryPi (I used Model 2B) + SD card for it
    • The script charge_limit.sh can be adapted also to your laptop etc.!
    • You would have to change the code to read the USB port ID and only disable the one your phone is connected to. I might add an example later.
  • A USB charger for the RPi (>2A / >10W)
  • 1 USB cable to supply power to the RPi
  • 1 USB cable to connect your phone to the RPi

Instructions:

The instructions are for advanced users. If there is enough demand I may simplify it for beginners. Otherwise you will have to google and follow instructions there, if a step is unclear. I might have forgotten some steps, let me now if it does not work.

  1. Install RPi OS on the SD card, if not preinstalled (choose username rpi if possible)
  2. Optional: Edit the /boot/config.txt on the SD to your likings (e.g. turn off LEDs, enable SSH)
  3. Boot the RPi with the SD card and continue either through SSH or with mouse, keyboard and display directly on the RPi
  4. Install packages sudo apt install adb uhubctl
  5. Put charge_limit.sh in the RPi home directory, and make it executable chmod +x charge_limit.sh
  6. Optional: change the charge limit in the file, standard is set to 80%
  7. Put 91-charge-limit.rules under /etc/udev/rules.d/91-charge-limit.rules
  8. Reload with: udevadm control --reload-rules && udevadm trigger
  9. Put charge_limit.service under /etc/systemd/system/charge_limit.service
  10. If you use a different RPi model to model 2, you have to change some code in charge_limit.sh, see the note below the files in this post further down.
  11. Reload systemd: sudo systemctl daemon-reload
  12. Enable ADB USB Debugging in the developer settings of your PHONE
  13. Connect your phone the first time with the RPi and accept the footprint permanently (the prompt that comes when you connect)
  14. It now should work, as long as ADB USB debugging is activated. If you deactivate it, your phone will just charge to 100% and there will be no smart features. If you activate ADB on your phone and you plug it into the RPi again, it should work again automatically.
  15. If it does not work, reboot the RaspberryPi and try connecting your phone again
  16. If it does work, disconnect your RaspberryPi from your home network to increase security and only connect if you want to do some changes that require internet connection

How to increase charging current beyond 500mA:

For RPi 2 and probably also the other models, BC 1.2 is not implemented. This is a protocol that tells the USB Hardware (i.e. your phone) that it can draw more current, up to 1.5A (7.5W). Alternatively there is USB PD, but also this is not implemented by RaspberryPi afaik.

So you would need to do two things:

  • Buy or develop additional hardware to put between RPi and your phone to implement BC 1.2
  • Set flags in the config.txt of RPi to allow maximum current, required on some models

The maximum RPi USB current varies between 0.5 and 1.6A, depending on model.

Here are the contens of the files, as FP does not allow me to attach it directly:

config.txt
# For more options and information see
# http://rpf.io/configtxt
# Some settings may impact device functionality. See link above for details

# uncomment if you get no picture on HDMI for a default "safe" mode
#hdmi_safe=1

# uncomment the following to adjust overscan. Use positive numbers if console
# goes off screen, and negative if there is too much border
#overscan_left=16
#overscan_right=16
#overscan_top=16
#overscan_bottom=16

# uncomment to force a console size. By default it will be display's size minus
# overscan.
#framebuffer_width=1280
#framebuffer_height=720

# uncomment if hdmi display is not detected and composite is being output
#hdmi_force_hotplug=1

# uncomment to force a specific HDMI mode (this will force VGA)
hdmi_group=1
#hdmi_mode=1

# uncomment to force a HDMI mode rather than DVI. This can make audio work in
# DMT (computer monitor) modes
hdmi_drive=2

# uncomment to increase signal to HDMI, if you have interference, blanking, or
# no display
#config_hdmi_boost=4

# uncomment for composite PAL
#sdtv_mode=2

#uncomment to overclock the arm. 700 MHz is the default.
#arm_freq=800

# Uncomment some or all of these to enable the optional hardware interfaces
#dtparam=i2c_arm=on
#dtparam=i2s=on
#dtparam=spi=on

# Uncomment this to enable infrared communication.
#dtoverlay=gpio-ir,gpio_pin=17
#dtoverlay=gpio-ir-tx,gpio_pin=18

# Increase USB Power Output to max 1.2A from 0.6A
max_usb_current=1

# LEDs off
dtparam=act_led_trigger=none
dtparam=pwr_led_trigger=none

#DVD screen auto turn on cec
#hdmi_ignore_cec_init=1

# Additional overlays and parameters are documented /boot/overlays/README

# Enable audio (loads snd_bcm2835)
dtparam=audio=on

# Automatically load overlays for detected cameras
camera_auto_detect=1

# Automatically load overlays for detected DSI displays
display_auto_detect=1

# Enable DRM VC4 V3D driver
dtoverlay=vc4-kms-v3d
max_framebuffers=2

# Disable compensation for displays with overscan
disable_overscan=1

[cm4]
# Enable host mode on the 2711 built-in XHCI USB controller.
# This line should be removed if the legacy DWC2 controller is required
# (e.g. for USB device mode) or if USB support is not required.
otg_mode=1

[all]

[pi4]
# Run as fast as firmware / board allows
arm_boost=1

[all]
charge_limit.sh
#!/bin/bash

# Script is triggered on device USB connection
# It determines device charge level with adb
# and if threshold is reached, it turns off the USB
# until 05:00 in the morning or for 1h, depending on 
# time of day.
# That way charging is limited to threshold percentage

threshold=80

# Wait for system to detect adb device
sleep 1

# Regulary check for level and if device is still there
while :
do
	# Get battery charge
	level=$(adb shell dumpsys battery >&1 2>/dev/null | grep level | tr -d -c 0-9)

	# If level is empty, no android adb enabled device
	if [ -z "$level" ]; then
		echo "No adb device"
		exit 0
	fi
	
	if (( level >= threshold )); then	
		break;
	fi
	
	sleep 1
done


# Level has reached threshold

# Turn off charging, and probe again later
uhubctl -l 1-1 -p 2 -a off

# If 23:00 - 05:00 => Turn USB off until 05:00
# Else turn off for 1 hour (@ 15%/h max discharge rate measured)


# Get current hour (24-hour format)
current_hour=$(date +%H)

if [ "$current_hour" -ge 23 ] || [ "$current_hour" -lt 5 ]; then
    # Calculate seconds until 05:00
    now=$(date +%s)
    tomorrow_5am=$(date -d '05:00' +%s)

    # If current time is after midnight but before 05:00, '05:00' is today
    # If current time is after 23:00, '05:00' is tomorrow
    if [ "$current_hour" -ge 23 ]; then
        tomorrow_5am=$(date -d 'tomorrow 05:00' +%s)
    fi

    wait_seconds=$(( tomorrow_5am - now ))
    echo "It's night time. Waiting until 05:00 ($wait_seconds seconds)..."
    sleep "$wait_seconds"
else
    echo "It's daytime. Waiting for 1 hour..."
    sleep 3600
fi

# Turn on USB
uhubctl -l 1-1 -p 2 -a on

# Script is automatically recalled if device is still connected on repower
91-charge-limit.rules
ACTION=="add", SUBSYSTEM=="usb", TAG+="systemd", ENV{SYSTEMD_WANTS}="charge_limit.service"
charge_limit.service
[Unit]
Description=Run USB Android charge limit script
After=multi-user.target

[Service]
Type=oneshot
ExecStart=/home/rpi/charge_limit.sh
User=root

[Install]
WantedBy=multi-user.target

ATTENTION: replace rpiwith your chosen username for the RPi, if you chose it differently from the instructions.

If you use a different RPi Model than Model 2:
You have to change the USB Port ID in the charge_limit.sh file at every spot uhubctl is used. It probably would make sense to adapt the code, to automatically read the USB port of the android device and turn off this group. On Model 2 all USBs are connected together in one group, so they can only turned off all together. In later models, USB ports are split in individual controllable groups. In theory you could also exploit this to allow the RPi to individually check and control charging of each of multiple android devices connected at the same time, if they are connected to individual groups.

I appreciate feedback to improve the code and the setup. :slight_smile:

1 Like
If there are enough votes (maybe 100) 
on this I might do it

Poll
  • Please make beginner friendly instructions
  • Please make instructions for other RPi Models
0 voters

Hey, thank you for sharing :slight_smile:

I thought I might mention how I did it, since it is very simple while having its own limitations…

I host Home Assistant on a Raspberry Pi (for my whole smart home) and plugged my charger into a basic 10€ smart-plug. Now I set a basic automation that will turn off the plug if the phones charge reaches 80% (Home Assistant app reports the charge to the host by default).

It’s a perfect solution for people who already use Home Assistant, less so for everyone else.

1 Like

Also an interesting approach. That way one doesn’t have to enable ADB, increasing security. But yeah, it does come with other difficulties.

You probably could also, if you don’t care about the slow charge, combine your approach with the control of the USB ports of your Pi and directly charge over it. That way you wouldn’t need the extra charger and the smart plug.

@Moderators please only modify the second post with the poll, otherwise I cannot update the original post to add information, as I am not allowed to create polls.

Well your power to change the initial post is also very limited, so it will not change a lot.

Yeah I know, after 30 days or so it is locked
But until then I might still alter it a few times

Can you actually “reopen” it to me later on, if I would decide to do some additions?

No the only we could do is make it a wiki, however that means almost everyone can edit.

Good to know, thanks. Let’s leave it like that for now.
Maybe I just create a Github repo at some time
Depending on the poll hahaha

But could you than create that poll in my second comment please?

I cant at the moment as I’m only online with my FP

With a normal Raspberry Pi OS or similar that should work afaict. On Home Assistant OS, access to hardware is limited and modifying the host system is risky and not recommended unfortunately. However, the GPIOs are accessible, so this could be accommodated with a GPIO-controlled switch.

OT:
I use Zigbee 3.0 devices and a Zigbee USB dongle that is connected to the Pi. I am about to switch to one of those, it can switch its USB-ports on/off:
image

Clearly not worth setting up for everyone but if ya could use a little bit automation here and there at your place or any of the many services HA provides, I would go for it! The setup is cheap, extendable (works with thousands of manufacturers), free, open (source), barely consumes any power and should last long.

1 Like

Oh I didn’t know it comes with a full OS, okay that makes it more complicated. But with the switchable USB supply, that’s nice.

Your setup sounds very nice, thanks for the tip! Home automation is definetly is on my list of todos. It is so much fun and I have a million ideas. Definetly need to join a hacker space at some point haha

1 Like

Just to comment that the FP 5 does let you limit the charge to 80%. I have selected that option on both my and my wife’s FP5s

1 Like

Oh that’s great, thanks for letting us know!

That gives me some hope that this feature eventually will also be integrated into FP4, when it finally gets the next big Android update. I just wonder when this will happen, after it has been delayed now for a loooong time and in repeated posts only promises and nothing concrete. But at least they say they still work on it and not yet have dropped it into oblivion.