Protecting the life of your phone battery

img/esp8266_sml.png

Charging your battery to 100% every night and then running it down to 0% is widely known to be bad for phone batteries, but unfortunately Android does not offer any way to stop charging your phone before it is full. It is possible to do this if you root your device but I chose a different approach.

Solution 1

My first attempt to solve this problem worked. I even shared it with my local electronics shop and they were kind enough to publish the project.

Charger controller version 1

I will put in a quick plug to recommend Core Electronics as they have many excellent tutorials and are a very friendly and helpful bunch.

The problems with my first attempt

If you have followed the version 1 link above then you will have seen that my solution involved using a Raspberry Pi to control a relay to switch off the phone charger when the phone sends an HTTP request when the charge reaches a certain battery percentage. The phone triggered the http request using the excellent Automate app.

The idea worked, but this first iteration proved unreliable and clunky for a number of reasons. Firstly, I had trouble with the relay. Relays are electromechanical devices that basically use an electromagnet to hold a switch open or closed. The issue here is that powering an electromagnet requires quite a few mA and the Raspberry Pi GPIO pins struggle to supply enough oomph. Secondly, I had numerous problems with keeping the Pi connected to WiFi which may have been due to the cheap, no-name, WiFi adapter, but could also have been influenced by the power draw from the relay. Thirdly, I didn’t read the manual which states “RTC in ESP8266 has very bad accuracy, drift may be seconds per minute” or in my case, hours over a night. Finally, I discovered some bugs in the way that the Automate program interacts with Android which meant that sometimes the charger was not turned off.

Solution 2

My new iteration of this design solves all of the above issues.

Firstly replacing the relay. Relays aren’t the only way to switch things on and off. Transistors can also work very well (at least for lower voltages) and require very small switching currents (unlike relays).

Core Electronics Mosfet for makers video

After watching this video I picked up a few of these Mosfet switch modules which work perfectly for this project.

Replacing the pi

The main job that the pi was performing was to receive an http request and to flip a GPIO pin. True, the pi I was using was an original 1B I had spare, but it was overkill for the job and I remembered I had an ESP8266 which might be suitable as it can run Micropython. Migrating from the pi to the ESP8266 was surprisingly simple. Something like this ESP32-C3 device would be perfect for the job (the ESP8266 is much older).

I followed this excellent getting started guide. In summary, you need to flash the ESP device with firmware using esptool.py. Once flashed you can connect to your device using a terminal like picocom which gives you a Python REPL on the device for testing. Once I had updated the code (which required very few changes) it can be deployed on the device using rshell to copy files directly onto the ESP device. To configure the WiFi you need to do this in your Python code. It is not ideal hardcoding this, but for my use case it is fine. The convention for the ESP Micropython firmware is that there is a boot.py which contains the minimal startup and calls main.py which is where I stored my code.

#!/usr/bin/python
# Script to stop phone charging when an http request is received
# To stop the phone charging a relay is connected to an ESP8266 GPIO pin
# JKenyon 2024

# Libraries
import network
import time
import ntptime
import socket
from machine import Pin

# Define constants
SERVER_HOST = '0.0.0.0'
SERVER_PORT = 8001
RELAY_PIN = 0
SLEEP_TIME = 14400 # 4 hours
SSID = 'SSID'
PASSWORD = 'PASSWORD'

# Function to connect to WiFi
def do_connect():
  wlan = network.WLAN(network.STA_IF)
  ap_if = network.WLAN(network.AP_IF)
  wlan.active(True)
  if not wlan.isconnected():
    wlan.connect(SSID, PASSWORD)
    while not wlan.isconnected():
      pass
    ap_if.active(False)

# Function to sleep using NTP instead of local time
def ntpSleep(seconds):
  # Sleep based on NTP time (to the nearest 3 minutes)
  # This is needed as the ESP8266 sleep is wildly inaccurate for large values 
  ntptime.settime()
  endTime=time.time() + seconds
  while time.time() < endTime:
    time.sleep(180)
    ntptime.settime()

# Define a simple web server that quits after it receives a connection
def waitForConnection():
  # https://www.codementor.io/@joaojonesventura/building-a-basic-http-server-from-scratch-in-python-1cedkg0842
  # Modified to exit when a connection is made
  # Create socket
  server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  server_socket.bind((SERVER_HOST, SERVER_PORT))
  server_socket.listen(1)
  # Wait until we receive any connection (as we don’t parse anything this is pretty safe)
  client_connection, client_address = server_socket.accept()
  response = 'HTTP/1.0 200 OK\n\n'
  client_connection.sendall(response.encode())
  client_connection.close()
  server_socket.close()

# Define a function to control the GPIO and switch off the charger
def switchOffChargerForTime(delay):
  p0.off() 
  ntpSleep(delay)
  p0.on()

# Main
# Initialise pin
p0 = Pin(0, Pin.OUT)
time.sleep(1)
p0.on() # turns on charging

# Connect to Wifi
do_connect()
time.sleep(10) # wait just to make sure it connects
while True:
  waitForConnection()
  switchOffChargerForTime(SLEEP_TIME)

The code implements a very minimal http server which instantly closes as soon as it receives a connection. The fact that it does not even attempt to parse the request should make it pretty secure (although I am not a security expert!).

Fixing time

You will notice in the code above an unpleasant but simple workaround for the excessive time drift. As it needs to poll an NTP server I thought it was polite to only check the time every ~3 minutes so it is up to 3 minutes late, but at least it counts hours correctly which is what I wanted.

def ntpSleep(seconds):
  # Sleep based on NTP time (to the nearest 3 minutes)
  # This is needed as the ESP8266 sleep is wildly inaccurate for large values 
  ntptime.settime()
  endTime=time.time() + seconds
  while time.time() < endTime:
    time.sleep(180)
    ntptime.settime()

Android and Automate

I was previously using the “Power source plugged?” block to test if the phone was on the charger. It seems that on occasion this does not report correctly which may be an Android issue. I realised that I don’t really need to check this as the only important information is the battery level so I removed this block. I also implemented a retry for the http request just in case there is an issue with the WiFi.

Final result

The updated solution is rock solid and will hopefully protect my phone battery for a long time.

Picture of final solution

Whole solution

Automate script

Automate script

Circuit diagram

Circuit diagram


Written By

John Kenyon

Sydney based techie