1

I have the following python code (in PyCharm) that I use to take readings from an Arduino board. The readings themselves are fine. I have the two following problems with the tkinter part of the code:

  1. The code begins to read in values from Arduino as soon as it is launched, whereas I want to initiate this on a button click ('read_data'); as long as I don't press the 'read_data' button, the graph is not displayed, but readings are taken; I can see that when I open the graph several seconds after I begin running the code;
  2. When I close the plot close_plot, the graph window is indeed closed, only to be re-opened a short while later.

I think the problem lies in Top.after as it is continuously run within the mainloop() and, therefore, the read_data function is continuously updated. How can I get around this?

import serial
from tkinter import *
from matplotlib import pyplot as plt

Top = Tk()

ser = serial.Serial('COM3', baudrate=9600, timeout=1)

x = []
y = []

def read_data():
    plt.ion()
    new_value = ser.readline().decode('ascii')
    if new_value == '':
        pass
    else:
        y.append(eval(new_value[:-2]))
        x.append(len(y) - 1)
        plt.plot(x, y, 'r-')
        plt.show()
        plt.pause(0.0001)
        Top.after(100, read_data)

def close_plot():
    plt.close()
    global x, y
    x = []
    y = []

def quit():
    Top.destroy()

Button(Top, text='Read', command=read_data).pack()
Button(Top, text='Close plot', command=close_plot).pack()
Button(Top, text='Quit', command=quit).pack()

Top.after(100, read_data)
mainloop()

Edit: when pressing the read_data button, I get the following warning:

C:\ProgramData\Anaconda3\lib\site-packages\matplotlib\backend_bases.py:2445: MatplotlibDeprecationWarning: Using default event loop until function specific to this GUI is implemented warnings.warn(str, mplDeprecation)
Nae
  • 10,363
  • 4
  • 30
  • 67
DenGor
  • 165
  • 4
  • 17
  • 1
    1. remove `Top.after(100, read_data)` before `mainloop()` - it starts reading 100ms after program start. – furas Dec 25 '17 at 16:11
  • 1
    BTW: read [PEP 8 -- Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) . We use "Upper case names" only for class names like `Button`, `Tk` or `Serial` but not for variables like `Top`. It makes code more readable. Even `Stackoverflow` knows this rule and uses light blue color for classes. – furas Dec 25 '17 at 16:16
  • @furas: I am afraid your suggestion doesn't help, the code still begins reading as soon as it gets run – DenGor Dec 25 '17 at 16:18
  • @DenGor What proof you have of that exactly? – Nae Dec 25 '17 at 16:31
  • @Nae the proof is the same as before - I run the code, wait a few seconds, open a graph that already contains several data points. – DenGor Dec 25 '17 at 16:33
  • 1
    did you remove correct `after()` - before `mainloop()`. If you still have problem then maybe you use different code than you show in question - ie. you may have `()` in `command=read_data()` – furas Dec 25 '17 at 16:38
  • No, furas, I did copy+paste. I removed top.after(100, read_data) just before mainloop() – DenGor Dec 25 '17 at 16:45
  • Then you have someting wrong in system - because it shouldn't run it without `after` - but I can't help. – furas Dec 25 '17 at 16:51
  • 2
    try to run it without PyCharm - maybe there is some problem with PyCharm. Or problem is with Anaconda - I tested only with standard Python installation and don't get error `"MatplotlibDeprecationWarning"`. – furas Dec 25 '17 at 18:00
  • @furas I've just run it in Spyder. There's good news and bad news: indeed, the error is gone but the code still behaves the way I described it in the beginning even when I copy your code from here – DenGor Dec 25 '17 at 19:16
  • run it without any IDE - run it normally in shell/console/terminal/cmd.exe using `python script.py`. – furas Dec 25 '17 at 19:26
  • @furas I beg your pardon. I overlooked the initial behavior of the code - now nothing is read as long as I don't press the button. The only problem left is the "close_plot" command – DenGor Dec 25 '17 at 19:29
  • so I problem resolved :) – furas Dec 25 '17 at 19:31
  • @DenGor What _is_ the problem with `close plot`? Below answer doesn't resolve it? – Nae Dec 25 '17 at 19:40
  • @Nae correct. When I press the button during data aquisition, the window gets closed and shortly thereafter opened again – DenGor Dec 25 '17 at 20:05
  • @furas Do you have any idea what might be wrong with PyCharm? – DenGor Dec 25 '17 at 20:49
  • it may have functions to display `Matplotlib` inside IDE and it can make problem. – furas Dec 25 '17 at 21:03

1 Answers1

1

First, remove the line:

Top.after(100, read_data)

that comes immediately before mainloop() like furas suggested.

Then add after_cancel method to stop calling read_data every 100 ms, but for that to work, we need to be assigning after we use inside the method to a global variable first:

func_id = Top.after(100, read_data)

and then finally call after_cancel in close_plot:

Top.after_cancel(func_id)

Your code should be exactly like below:

import serial
from tkinter import *
from matplotlib import pyplot as plt

Top = Tk()

ser = serial.Serial('COM3', baudrate=9600, timeout=1)

x = []
y = []
func_id = None

def read_data():
    global func_id
    plt.ion()
    new_value = ser.readline().decode('ascii')
    if new_value == '':
        pass
    else:
        y.append(eval(new_value[:-2]))
        x.append(len(y) - 1)
        plt.plot(x, y, 'r-')
        plt.show()
        plt.pause(0.0001)
    func_id = Top.after(100, read_data)

def close_plot():
    global func_id
    #to no longer update the plot
    Top.after_cancel(func_id)
    plt.close()
    global x, y
    del x[:]
    del y[:]

def quit():
    Top.destroy()

Button(Top, text='Read', command=read_data).pack()
Button(Top, text='Close plot', command=close_plot).pack()
Button(Top, text='Quit', command=quit).pack()

mainloop()
Nae
  • 10,363
  • 4
  • 30
  • 67
  • I would put `after()` outside `if/else` so it would read all time - but I can't test it. – furas Dec 25 '17 at 16:50
  • 1
    BTW: I try to test it on Linux using `socat` to create fake serial port -like in answer https://stackoverflow.com/a/23255001/1832058 – furas Dec 25 '17 at 16:53
  • I appreciate your help but I can't get it working, even when I copy your code. The errors are repeated. Additionally, I get the following warning: MatplotlibDeprecationWarning: Using default event loop until function specific to this GUI is implemented warnings.warn(str, mplDeprecation) – DenGor Dec 25 '17 at 17:04
  • @DenGor Please append the MCVE code piece that results in that error and also post full error message too, to your question. – Nae Dec 25 '17 at 17:07
  • @DenGor Updated the answer here, now it should be working. – Nae Dec 25 '17 at 20:24
  • @Nae you are a genius! It's working! Now, could you explain what global func_id changed? – DenGor Dec 26 '17 at 16:41
  • @DenGor It just ensures the `func_id` is shared among the two methods, `close_plot` and `read_data`. It needs to be present in order to be able to use `after_cancel`. – Nae Dec 26 '17 at 16:47