Skip to content

Instantly share code, notes, and snippets.

@jbaiter
Last active January 11, 2021 21:38
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jbaiter/39d686325062f3058236267490a2785b to your computer and use it in GitHub Desktop.
Save jbaiter/39d686325062f3058236267490a2785b to your computer and use it in GitHub Desktop.
Small script to inhibit charging when powering a USB-PD Thinkpad with a USB-C powerbank.
ACTION=="change", SUBSYSTEM=="power_supply", RUN+="/usr/local/sbin/powerbank"
#!/usr/bin/python3
"""
Adjust the value of POWERBANK_WATTS to match your Powerbank's wattage.
If the wattage is identical to your actual charger, you're out of luck,
since the script uses this to differentiate a powerbank from an actual charger :-/
Requirements:
- acpi_call kernel module
- python-notify2
- tlp
Only tested on a T480s, the ACPI calls may differ for other models.
"""
import ctypes
import os
import subprocess
import time
import notify2
DISPLAY = ":0"
XAUTHORITY = os.path.expanduser('~/.Xauthority')
POWERBANK_WATTS = 31
DEFAULT_THRESHOLDS = (85, 90)
POWERBANK_THRESHOLDS = (20, 25)
TLP_MODES = {
'battery (manual)': 'bat',
'AC (manual)': 'ac',
'battery': 'bat',
'AC': 'ac',
}
def call_acpi(cmd, *args):
if args:
cmd += ' 0x'
cmd += ''.join(hex(arg)[2:] if isinstance(arg, int) else arg
for arg in args)
with open('/proc/acpi/call', 'wb') as fp:
fp.write(cmd.encode('utf8'))
with open('/proc/acpi/call', 'rb') as fp:
val = ctypes.create_string_buffer(fp.read()).value
try:
return int(val, 0)
except:
return val
def get_tlp_mode():
tlp_stat_cmd = ['/usr/bin/tlp-stat', '-s']
if not os.path.exists(tlp_stat_cmd[0]):
tlp_stat_cmd = ['/usr/sbin/tlp', 'stat', '-s']
out = subprocess.check_output(tlp_stat_cmd)
modeline = next(l for l in out.split('\n') if n.startswith('Mode'))
mode = modeline.split('=')[1].strip().tolower()
return TLP_MODES.get(mode, mode)
def set_tlp_mode(mode=None):
if mode and mode not in ('ac', 'bat'):
raise ValueError("Invalid mode '{}', mut be 'ac' or 'bat'".format(mode))
subprocess.check_output(['/usr/sbin/tlp', mode or 'auto'])
def get_power_supply_watts():
return call_acpi('\_SB.PCI0.LPCB.EC.HWAT')
def set_charging_thresholds(start=0, stop=0):
call_acpi('\_SB.PCI0.LPCB.EC.HKEY.BCCS', 1, start)
call_acpi('\_SB.PCI0.LPCB.EC.HKEY.BCSS', 1, stop)
def get_charging_thresholds():
start = call_acpi('\_SB.PCI0.LPCB.EC.HKEY.BCTG', 1)
stop = call_acpi('\_SB.PCI0.LPCB.EC.HKEY.BCSG', 1)
return start, stop
def main():
time.sleep(3)
if not os.environ.get('DISPLAY'):
os.environ['DISPLAY'] = DISPLAY
if not os.environ.get('XAUTHORITY'):
os.environ['XAUTHORITY'] = XAUTHORITY
notify2.init('powerbank-notifier')
wattage = get_power_supply_watts()
thresholds = get_charging_thresholds()
if thresholds == POWERBANK_THRESHOLDS:
return
elif 0 < wattage <= POWERBANK_WATTS:
notify_header = "Detected Powerbank"
notify_msg = 'Inhibiting charging, switching TLP to battery mode'
set_tlp_mode('bat')
set_charging_thresholds(*POWERBANK_THRESHOLDS)
elif wattage > 0:
notify_header = "Detected {}W charger".format(wattage)
notify_msg = "Re-enabling charging, switching TLP to AC mode"
set_tlp_mode('ac')
set_charging_thresholds(*DEFAULT_THRESHOLDS)
else:
notify_header = "Running from internal battery"
notify_msg = "Re-enabling charging, switching TLP to auto mode"
set_tlp_mode(None)
set_charging_thresholds(*DEFAULT_THRESHOLDS)
if notify_msg is not None:
notification = notify2.Notification(notify_header, notify_msg)
notification.show()
if __name__ == '__main__':
try:
main()
except Exception as e:
print(e)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment