simple tkinter battery monitor

Discussion in 'Python' started by leonix.power, Jan 27, 2013.

  1. leonix.power

    leonix.power Guest

    I tried to write a simple battery monitor for laptops which shows normally just the battery percentage, and when is clicked some more info.
    If I click just one time it works, but if I click a second time, the additional info Label seems to be empty (but it holds the dimension of the StringVar content, even if it isn't displayed)
    Where am I wrong?
    ---------------------------------------------------
    #!/usr/bin/python3.2

    from re import findall, search
    from threading import Thread
    from time import sleep
    from subprocess import Popen, call, PIPE, STDOUT
    from tkinter import *



    class battery_monitor:

    def __init__(self):
    root=Tk()
    # root.geometry("-0+0")
    root.overrideredirect(True)
    root.wm_attributes("-topmost", 1)
    self.battery_string=StringVar()
    self.battery_percent=StringVar()
    self.battery_label=Label(root, extvariable=self.battery_string, font=("fixed", 9))
    self.battery_icon=Label(root, textvariable=self.battery_percent, font=("fixed", 9), width=3)
    self.battery_icon.grid()
    t=Thread(target=self.update_battery_level_loop)
    t.start()
    root.bind("<Button-1>", self.display_details)
    self.root=root
    root.mainloop()

    # displays a message about details of battery status
    # i.e. "on-line" or "charging, 20 min left" and so on
    def display_details(self, event):
    self.battery_icon.grid_remove()
    self.battery_label.grid()
    self.root.update_idletasks()
    sleep(1)
    self.battery_label.grid_remove()
    self.battery_icon.grid()
    self.root.update_idletasks()

    # dummy function used just to test the GUI
    def read_battery_level(self):
    self.level=100
    return "battery is full"

    # threaded function, should constantly update the battery level
    def update_battery_level_loop(self):
    self.read_battery_level()
    while True:
    self.battery_percent.set(self.level)
    self.battery_string.set(self.read_battery_level())
    sleep(5)





    ##############################################
    #
    # main

    battery_monitor()
     
    leonix.power, Jan 27, 2013
    #1
    1. Advertisements

  2. leonix.power

    Dave Angel Guest

    See inline comment.
    I don't know tkinter very well, and I don't have 3.2, but the keyword
    seems to be wrong. Don't you want textvariable= ??

    Never use sleep() inside a gui's main thread. There are other ways to
    delay the UI without locking it up.
    This sleep() is okay.
     
    Dave Angel, Jan 27, 2013
    #2
    1. Advertisements

  3. leonix.power

    Rick Johnson Guest

    Before i discover your code logic error, i want to expose style errors.
    I am wondering why you would "selectively" import from the re module, which is a quite normal sized module, but then do the global import from tkinter, of which who's namespace is terribly polluted due to lack of packaging.
    Bad naming convention here! ALL class identifiers must (at the very least) /start/ with a capital letter. My strong opinion is to cap EVERY word. So for example:

    OptionMenu NOT Optionmenu
    ComboBox NOT Combobox
    etc...

    Using "lowercase_with_underscores" should be reserved for interface methods and global functions.
    I just hate when people use 1 and 0 for True and False. I know Python allows such non-sense, but i just hate it because it can cause subtle bugs -- and most logical people agree with me.
    Two issues here. First you use a function/interface style to define a variable. Then you go and add insult to injury by using an ambiguous name. You should have used something like: "batteryStringVar" and "batteryPercentVar".
    Don't assign variables without leaving buffer spaces around the equals sign. Only omit the spaces when passing arguments to a class, method, or function.
    Oh gawd this is a major pet peeve of mine!!!!

    Always place a comment at the same indention level of the function or class that it references. AND NEVER, EVER, place a comment OUTSIDE of a function, method, or class like you have done here.
    You may want to look into the Universal Tkinter widget method: "w.after(ms, func)".

    Finally, i don't understand why you are using such a deep indention level. Unknown Source said: "Four spaces thou shalt indent, and the number of thou indention shall be four."

    Only after you clean up these style abominations by submitting a new example of your code will i offer any more help. Thanks.
     
    Rick Johnson, Jan 28, 2013
    #3
  4. leonix.power

    leonix.power Guest

    Thank you very much! fixed with w.after
    Here is the code, works under Linux for those who have acpi.
    My output of "acpi -V" is the following, the code is parsing the first line of the output. Any improvements are appreciated.

    ----------------------------------------------------------
    ----------------------------------------------------------
    ----------------------------------------------------------


    #!/usr/bin/python3.2

    from re import findall, search
    from threading import Thread
    from time import sleep
    from subprocess import Popen, call, PIPE, STDOUT
    from tkinter import Tk, Label, StringVar



    def runProcess(exe):
    p=Popen(exe, stdout=PIPE, stderr=STDOUT)
    while True:
    retcode=p.poll()
    line=p.stdout.readline()
    yield line
    if retcode is not None:
    break


    class BatteryMonitor:

    def __init__(self):
    root = Tk()
    root.configure(padx=1, pady=1, bg="#555753")
    root.geometry("-0+0")
    root.overrideredirect(True)
    root.wm_attributes("-topmost", True)
    self.batteryExtendedStringVar = StringVar()
    self.batteryPercentStringVar = StringVar()
    self.batteryExtendedLabel = Label(root, textvariable=self.batteryExtendedStringVar, font=("fixed", 9), bg="#3e4446", fg="#d3d7cf", padx=10, pady=-1)
    self.batteryPercentLabel = Label(root, textvariable=self.batteryPercentStringVar, font=("fixed", 9), width=4, bg="#3e4446", fg="#d3d7cf", padx=-1, pady=-1)
    self.batteryPercentLabel.grid()
    t = Thread(target=self.update_battery_level_loop)
    t.start()
    root.bind("<Button-1>", self.display_details)
    self.root = root
    root.mainloop()

    def display_details(self, event):
    # displays a message about details of battery status
    # i.e. "on-line" or "charging, 20 min left" and so on
    self.batteryPercentLabel.grid_remove()
    self.batteryExtendedLabel.grid()
    self.batteryExtendedLabel.after(1000, self.batteryExtendedLabel.grid_remove)
    self.batteryPercentLabel.after(1000, self.batteryPercentLabel.grid)

    def read_battery_level(self):
    # dummy function used just to test the GUI
    for line in runProcess(["acpi", "-V"]):
    if line[11:-1]!=b"on-line":
    self.level = findall(b"\d\d?", line[11:])[0]
    else:
    self.level = b"0"
    return line[11:-1]

    def update_battery_level_loop(self):
    # threaded function, should constantly update the battery level
    self.read_battery_level()
    while True:
    self.batteryPercentStringVar.set(str(self.level)[2:-1]+"%")
    self.batteryExtendedStringVar.set(self.read_battery_level())
    if self.level == 2:
    runProcess(["shutdown", "-h", "now"])
    return
    sleep(5)





    ##############################################
    #
    # main

    BatteryMonitor()
     
    leonix.power, Jan 30, 2013
    #4
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.