diff --git a/README.md b/README.md index 38b2a47..05b3bd5 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,9 @@ A bunch of simple bash scripts to be called via i3blocks. * **wifiinfo.sh**: Echos current db level of signal strength. * on left click: Uses nmcli to echo all device status. +## i3block_py +A collection of standalone python scripts for the slightly more complicated things. +* **energy.py**: parses `acpi` to output current battery status. Uses `notify-send` to send messages on changes. ## weather_applet Standalone script that pulls weather data from [openweathermap.org](https://openweathermap.org/) and prints out diff --git a/i3block_py/energy.py b/i3block_py/energy.py new file mode 100755 index 0000000..ff5039b --- /dev/null +++ b/i3block_py/energy.py @@ -0,0 +1,164 @@ +#!/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() + 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 + 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)) + 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()