0

I want to code a script, that has several different functions in which an array filled with serial data has to be received. The serial data comes from an arduino every 1 seconds. (don't worry. I changed the code to a reproducible example by using a random array.)

What I've succeeded in so far is, that the code does send the array into the function example ONCE the first time and displays it as I want it to.

What it does not do yet, is, that the information inside the function gets updated as it comes in from the arduino. When you see the code, you're gonna say, well the data is only sent once. BUT when I randomize the array every second inside a loop, the loop obviously blocks the rest of the code and the gui won't build. The fact is, that serial read updates the array WITHOUT a loop, which is highly appreciated.

The question is: How do I transport this updating into the function. Remember: It would be the natural solution for the code below to simply insert the serial read stuff INSIDE the function. BUT this is just the code that boils down the issue. The real code has several widgets invoked inside several functions and I ended up copy-&-pasting THE ENTIRE serial data signal conditioning code block into EVERY function that needs the data. This significantly increased the lag of the code, and thus is no solution.

The example script contains commented out sections to make it easier to follow what I've been trying to do to solve it so far:

import numpy as np
#import rhinoscriptsyntax as rs
import time
import tkinter as tk
import serial

"""
ser = serial.Serial(
 port='/dev/ttyUSB0',
 baudrate = 500000,
 #parity=serial.PARITY_NONE,
 #stopbits=serial.STOPBITS_ONE,
 #bytesize=serial.EIGHTBITS,
 timeout=1
)
ser.flushInput()
ser.flushOutput()

#I've been trying to embed the serial read stuff inside a function itself, which I'd LOVE to implement,
#but it has the same problem: either it is called just once as it is written here, ore a loop blocks the code

def serial_data():
    #serialData = ser.readline()
    #serialData = serialData.decode()
    #floats = [float(value) for value in serialData.split(',')]
    #arr = np.array(floats)
    arr = np.random.rand(100)
    time.sleep(1)
    return arr
"""
#above commented out and replaced with random array below. 
#serialData = ser.readline()
#serialData = serialData.decode()
#floats = [float(value) for value in serialData.split(',')]
#arr = np.array(floats)

arr = np.random.rand(100)
time.sleep(1)
print(np.round(arr, 3))

def nextWindow(root):
    frame1 = tk.Frame(root, width=800, height=500)
    frame1.pack()
    text= tk.Text(frame1, width=80, height= 12, bd=0)
    text.pack()
    text.delete("1.0",tk.END)
    #serialData = serial_data()
    #text.insert(tk.INSERT, serialData)
    text.insert(tk.INSERT, np.round(arr, 3))
    text.update()

root = tk.Tk()
root.title('PythonGuides')
root.geometry('300x200')
root.config(bg='#4a7a8c')
nextWindow(root)

root.mainloop()
Riccardo
  • 13
  • 4
  • isn't `text.delete("1.0",tk.END)` kinda pointless? there is nothing to delete so why are you deleting anything? – Matiiss Nov 04 '21 at 16:59
  • To answer your first comment: yno. The real code is supposed to write the serial array into the text widget and whenever the array is updated, the contents of the text widget are deleted and the new array contents written. So, it really depends upon the answer as to if this is pointless or not. I left it in because it doesn't do harm and it documents a little what I've been fiddling with. Your second answer is too general for me to follow as being only an advanced beginner. I'd need an answer, that is explained in relation to my code to understand it. – Riccardo Nov 04 '21 at 17:23
  • is `serial_data` function fast? or at least is the time take to completely execute it small enough (can humans perceive the difference?), also you don't need to create a text widget every time you call the `newWindow`, create the text widget in the root, then you can use after, I will show you an example – Matiiss Nov 04 '21 at 17:48
  • No, the text widget needs to be in a function, not in root, because otherwise I can't implement it in my multiwindowed GUI code. The widget only exists in one window of the GUI, so, it's not in the root level, because root is going to be another window. – Riccardo Nov 04 '21 at 19:15
  • but it doesn't need to be created in the function, maybe it needs to be put on the window in a function, but the sole point of the function I have demonstrated is to update the text widget, not create it – Matiiss Nov 04 '21 at 19:19

1 Answers1

0

A minimal example as to how to use .after "loops" (explanation in code comments):

import tkinter as tk

# for the example
counter = 1


# your serial_data function, renamed to
# be more self-explanatory
def get_serial_data():
    # put the serial reading stuff here
    # the counter is just an example
    global counter
    counter += 1
    return str(counter)


def update_text(txt):
    # get data, clear text widget, insert new data
    serial_data = get_serial_data()
    txt.delete('0.0', 'end')
    txt.insert('end', serial_data)
    # schedule this function to run again in 100ms
    # so it will repeat all of this effectively
    # updating the text widget, you don't need to call `.update`
    # note that this is not recursive
    root.after(100, update_text)


def create_text():
    text = tk.Text(root)
    text.pack()
    update_text(text)


root = tk.Tk()

create_text()

root.mainloop()
Matiiss
  • 5,970
  • 2
  • 12
  • 29
  • I think we're getting there. I think now I understand it. The code example really did help me to grasp it. I appreciate your effort. I'm going to try out the newly learned "skill points" and report back – Riccardo Nov 04 '21 at 19:35
  • SOOOO I tested out everything and it all works either with a random array or real serial data. But what do I do when the text widget is not on the root window, but defined in another function? I tried adding a third function, but I get the typical problem with the sorting. If one function is placed last, it gives me errors, because something is not defined yet. And if I place it somewhere else, another function cries that something isn't defined. The only difference is, that "text = tk.Text(frame1,..." is in another function and it is NOT childed with root. Do I solve this with lambda? – Riccardo Nov 04 '21 at 20:18
  • @Riccardo the fact that its parent is not root doesn't matter, what you possibly need to do if defining a `Text` (creating new widget) in some function, use `global text` and only after calling the function that creates this text call the updater, or just call the updater in the function that creates the text, I will show an example – Matiiss Nov 04 '21 at 20:22
  • Yep, working. Thank you for the effort. It really felt like a team! – Riccardo Nov 04 '21 at 20:25
  • @Riccardo updated answer, the master doesn't matter here so you can place the text widget in a frame or wherever you want, just pass the argument of that instance to the updater, also you may want to maybe take a look at the [`Message`](https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/message.html) widget, it is similar to `Text`. It doesn't have the tags or marks but it can easily (and is meant to) display multi line text and the changing of text would be only one line `message.config(text=new_text)`, it is something in between a `Label` and `Text`. Also unlike `Text` user can't edit it – Matiiss Nov 04 '21 at 20:26
  • @Riccardo Please see [What to do when someone answers my question?](https://stackoverflow.com/help/someone-answers) and [How does accepting an answer work?](https://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work), this will help future readers and also allow to mark other questions as duplicates. Thank you! – Matiiss Nov 04 '21 at 20:28