#!/usr/bin/env python3 """ parses output of acpi to monitor battery status """ import subprocess LOG_FILE_PATH = "/tmp/battery_status.log" def get_acpi(): """ get acpi data as a sting """ acpi_raw = subprocess.run(["acpi"], capture_output=True, check=False) acpi_string = acpi_raw.stdout.decode().strip().split('\n')[0] return acpi_string def parse_acpi(acpi): """ split acpi string into its parts """ state_list = acpi.split(': ')[1].split(',') battery_state_list = [i.strip() for i in state_list] # catch different states if len(battery_state_list) == 2: # fully charged battery_state, battery_level = battery_state_list time_remaining = False elif len(battery_state_list) == 3: # not fully charged battery_state, battery_level, time_remaining = battery_state_list time_remaining = time_remaining.split()[0] time_remaining = parse_time(time_remaining) battery_level = battery_level.strip('%') return battery_state, battery_level, time_remaining def parse_time(time_remaining): """ returns minutes remaing from 00:00:00 format if a string else parses minutes back to hh:mm """ if isinstance(time_remaining, int): if time_remaining > 60: hour = int(str(time_remaining / 60).split('.')[0]) minutes = time_remaining - ( hour * 60 ) hour_c = str(hour).zfill(2) minutes_c = str(minutes).zfill(2) left = f'{hour_c}:{minutes_c}' else: minutes_c = str(time_remaining).zfill(2) left = f'00:{minutes_c}' else: time_list = time_remaining.split(':') time_list.reverse() # account for different length of str total_sec = int() for i in enumerate(time_list): position, value = [int(j) for j in i] sec = value * 60 ** position total_sec = total_sec + sec # normalize to minutes left = int(total_sec / 60) return left def get_last_log(battery_state, time_remaining): """ parse log file and return new average min value """ new_line = f'{battery_state} {time_remaining}' try: with open(LOG_FILE_PATH, 'r') as last_log_file: log_lines = last_log_file.readlines() except FileNotFoundError: # no log file on first run return time_remaining, new_line # get lines try: log_lines_clean = [i.strip() for i in log_lines] last_10 = [i.split() for i in log_lines_clean] min_list = [int(i[1]) for i in last_10[-2:]] min_list.append(time_remaining) # calc avg avg_min = int(sum(min_list) / len(min_list)) except ValueError: # something went wrong, reset log file return time_remaining, new_line return avg_min, last_10 def write_log(last_10, battery_state, new_avg_min): """ update the log file """ state_changed = False if isinstance(last_10[0], type('str')): log_list = [battery_state, str(new_avg_min)] log_string = ' '.join(log_list) elif last_10[-1][0] == battery_state: # last state is same as current state last_10.append([battery_state, str(new_avg_min)]) log_list = last_10[-10:] log_string = '\n'.join([' '.join(i) for i in log_list]) else: # state changed replace log file if battery_state == 'Full': new_avg_min = 0 log_string = f'{battery_state} {new_avg_min}' state_changed = True # write to log file with open(LOG_FILE_PATH, 'w') as log_file: log_file.write(log_string + '\n') return state_changed def print_status(battery_state, battery_level, avg_min, state_changed): """ will print three lines for i3blocks """ print_main = "" print_small = "" print_color = "" battery_level_int = int(battery_level) left = parse_time(avg_min) if battery_state == 'Discharging': if battery_level_int <= 20: icon = "" print_main = " $battery_level_int% $left" print_main = f'{icon} {battery_level_int}% {left}' print_color = "#ffffff" elif 20 < battery_level_int <= 25: icon = "" print_main = f'{icon} {battery_level_int}% {left}' print_color = "#ff0000" elif 25 < battery_level_int <= 40: icon = "" print_main = f'{icon} {battery_level_int}% {left}' print_color = "#ffff00" elif 40 < battery_level_int <= 70: icon = "" print_main = f'{icon} {battery_level_int}% {left}' elif 70 < battery_level_int <= 90: icon = "" print_main = f'{icon} {battery_level_int}% {left}' else: icon = "" print_main = f'{icon} {battery_level_int}% {left}' elif battery_state == 'Charging': icon = "⚡" print_main = f'{icon} {battery_level_int}% {left}' print_small = f'{icon} {battery_level_int}%' if battery_level_int <= 20: print_color = "#E53935" else: print_color = "#ffffff" else: # full icon = "⚡" print_main = f'{icon} {battery_level_int}%' print_small = f'{icon}' if state_changed: subprocess.call(["notify-send", f'{icon} {battery_state}']) # print print(print_main) print(print_small) print(print_color) def main(): """ main to run """ acpi = get_acpi() battery_state, battery_level, time_remaining = parse_acpi(acpi) new_avg_min, last_10 = get_last_log(battery_state, time_remaining) state_changed = write_log(last_10, battery_state, new_avg_min) print_status(battery_state, battery_level, new_avg_min, state_changed) if __name__ == '__main__': main()