I want GUI to update within the loop but it updates only when the loop ends all the data within the screen and the screen itself doesn't change.
I tried threading and kivy.clock.Clock but nither of them worked.
My objective is to make this turtle move from GUI: - turtlesim window.
This is how my GUI home looks like: - Kivy GUI.
When I give turtle a target and hit 'Go' button from GUI home screen, the screen should change immediately and the changed screen should show the real time turtle coordinates. But instead what happens is that when the turtle reaches the given coordinates then the screen changes the the final coordinates is only displayed.
Changed screen and the last turtle coordinates: - changed screen
Final turtle position: - turtlesim window
This is my abit lengthy code: -
import rclpy
import threading
from geometry_msgs.msg import Twist
from turtlesim.msg import Pose
from math import atan2, pi
from matplotlib.pyplot import pause
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.core.window import Window
from kivymd.uix.datatables import MDDataTable
from kivy.metrics import dp
from kivy.config import Config
from kivy.clock import Clock
print('code started') # remove line when done
Config.set('graphics','resizable', False)
Window.size = (720, 480)
def isfloat(num):
try:
float(num)
return True
except ValueError:
return False
class Gototarget(MDApp):
print('App class started') # remove line when done
pose_x = None
pose_y = None
pose_theta = None
def __init__(self, **kwargs):
self.title = "node: '/gototarget'"
super().__init__(**kwargs)
def pose_listener(self, msg_sub_pose):
self.pose_x = msg_sub_pose.x
self.pose_y = msg_sub_pose.y
self.pose_theta = msg_sub_pose.theta
print('pose_listener ran') # remove line when done
def target_parameters(self, pose_x, pose_y, input_x, input_y):
dist = pow((pow((input_x - pose_x),2) + pow((input_y - pose_y),2)),0.5)
dirn = atan2((input_y - pose_y),(input_x - pose_x))
print(f'''
x coordinate : {round(pose_x,3)}
y coordinate : {round(pose_y,3)}
tar. distance : {round(dist,3)}''')
return dist, dirn
def turtle_state(self):
print(f'''\nCurrent turtle state is: -
x : {self.pose_x}
y : {self.pose_y}
theta : {self.pose_theta}
''')
def user_input_call(self):
print('user_input_call started') # remove line when done
while not self.root.ids.go.state == 'down':
pause(0.1)
inp_x = float(self.root.ids.x_coordinate.text)
inp_y = float(self.root.ids.y_coordinate.text)
print('user_input_call ended') # remove line when done
return inp_x, inp_y
def build(self):
print('build started') # remove line when done
rclpy.init()
self.node = rclpy.create_node('gototarget')
self.pub_operator = self.node.create_publisher(Twist, 'turtle1/cmd_vel', 10)
self.sub_pose = self.node.create_subscription(Pose, 'turtle1/pose', self.pose_listener, 10)
self.msg_pub_operator = Twist()
print('node created') # remove line when done
rclpy.spin_once(self.node)
print('node spin first time') # remove line when done
self.input_x = float(self.pose_x)
self.input_y = float(self.pose_y)
self.spawn_x = self.input_x
self.spawn_y = self.input_y
# self.update_screen = Clock.create_trigger(self.root.ids.sm.current)
self.update_data = Clock.create_trigger(self.dataupdate)
self.root = Builder.load_string(
'''
#:kivy 2.1.0
#:import NoTransition kivy.uix.screenmanager.NoTransition
MDScreen:
MDToolbar:
id: toolbar
title: 'Command Turtle'
pos_hint: {'top': 1}
elevation: 15
left_action_items: [['menu', lambda x: nav_drawer.set_state("open")]]
MDNavigationLayout:
id: nav_layout
ScreenManager:
id: sm
transition: NoTransition()
MDScreen:
name: 'HomeScreen'
MDLabel:
text: 'Enter target location for the turtle'
pos_hint: {"center_y": 0.8}
halign: 'center'
MDTextField:
id: x_coordinate
hint_text: 'x coordinate'
pos_hint: {"center_x": 0.43, "center_y": 0.71}
size_hint: 0.12, None
helper_text_mode: 'persistent'
helper_text: ''
helper_text_color_normal: 1, 0, 0, 1
helper_text_color_focus: 1, 0, 0, 1
on_text:
self.helper_text = ''
MDTextField:
id: y_coordinate
hint_text: 'y coordinate'
pos_hint: {"center_x": 0.57, "center_y": 0.71}
size_hint: 0.12, None
helper_text_mode: 'persistent'
helper_text: ''
helper_text_color_normal: 1, 0, 0, 1
helper_text_color_focus: 1, 0, 0, 1
on_text:
self.helper_text = ''
MDRaisedButton:
id: go
text: 'Go'
pos_hint: {"center_x": 0.5, "center_y": 0.59}
elevation: 10
on_press:
app.textcheck()
MDSeparator:
pos_hint: {"center_y": 0.5}
MDLabel:
text: 'Current turtle state is: -'
pos_hint: {"center_x": 0.25, "center_y": 0.37}
halign: 'center'
MDLabel:
text: 'x :'
size_hint_x: 0.25
pos_hint: {"center_x": 0.25, "center_y": 0.25}
halign: 'right'
MDLabel:
text: 'y :'
size_hint_x: 0.25
pos_hint: {"center_x": 0.25, "center_y": 0.2}
halign: 'right'
MDLabel:
size_hint_x: 0.25
text: 'theta :'
pos_hint: {"center_x": 0.25, "center_y": 0.15}
halign: 'right'
MDLabel:
id : x
size_hint_x: 0.25
text: str(app.pose_x)
pos_hint: {"center_x": 0.55, "center_y": 0.25}
halign: 'left'
MDLabel:
id : y
size_hint_x: 0.25
text: str(app.pose_y)
pos_hint: {"center_x": 0.55, "center_y": 0.2}
halign: 'left'
MDLabel:
id : theta
size_hint_x: 0.25
text: str(app.pose_theta)
pos_hint: {"center_x": 0.55, "center_y": 0.15}
halign: 'left'
MDScreen:
name: 'TurtlePose'
MDLabel:
text: 'Current turtle state: -'
size_hint_x: 0.3
pos_hint: {"center_x": 0.15, "center_y": 0.67}
halign: 'right'
MDLabel:
id: turtle_state
text: 'Running'
size_hint_x: 0.25
pos_hint: {"center_x": 0.45, "center_y": 0.67}
halign: 'left'
MDLabel:
text: 'x coordinate :'
size_hint_x: 0.25
pos_hint: {"center_x": 0.25, "center_y": 0.55}
halign: 'right'
MDLabel:
text: 'y coordinate :'
size_hint_x: 0.25
pos_hint: {"center_x": 0.25, "center_y": 0.5}
halign: 'right'
MDLabel:
size_hint_x: 0.25
text: 'target distance :'
pos_hint: {"center_x": 0.25, "center_y": 0.45}
halign: 'right'
MDLabel:
id: disp_x
size_hint_x: 0.30
text: ''
pos_hint: {"center_x": 0.58, "center_y": 0.55}
halign: 'left'
MDLabel:
id: disp_y
size_hint_x: 0.30
text: ''
pos_hint: {"center_x": 0.58, "center_y": 0.5}
halign: 'left'
MDLabel:
id: disp_dis
size_hint_x: 0.30
text: ''
pos_hint: {"center_x": 0.58, "center_y": 0.45}
halign: 'left'
MDRaisedButton:
text: 'Enter another target'
pos_hint: {"center_x": 0.35, "center_y": 0.3}
elevation: 10
size_hint: None, None
width: root.width*0.2
on_press: sm.current = 'HomeScreen'
MDRaisedButton:
text: 'Target History'
pos_hint: {"center_x": 0.65, "center_y": 0.3}
elevation: 10
size_hint: None, None
width: root.width*0.2
on_press: sm.current = 'TargetHistory'
MDScreen:
name: 'TargetHistory'
id: 'history_screen'
MDLabel:
text: 'Target history-coordinates are as follows: -'
size_hint_x: 0.5
pos_hint: {"center_x": 0.3, "center_y": 0.75}
halign: 'left'
MDRaisedButton:
text: 'Enter another target'
pos_hint: {"center_x": 0.35, "center_y": 0.125}
elevation: 10
size_hint: None, None
width: root.width*0.2
on_press: sm.current = 'HomeScreen'
MDRaisedButton:
text: 'Exit'
pos_hint: {"center_x": 0.65, "center_y": 0.125}
elevation: 10
size_hint: None, None
width: root.width*0.2
on_press: app.stop()
MDScreen:
name: 'AboutApp'
MDLabel:
text: 'about'
MDNavigationDrawer:
id: nav_drawer
orientation: 'vertical'
padding: 10
MDLabel:
text: 'Command Turtle'
size_hint_y: 0.1
MDSeparator:
MDSeparator:
Widget:
size_hint: None, 0.005
MDSeparator:
MDSeparator:
ScrollView:
padding: 10
MDList:
padding: 10
OneLineListItem:
text: 'Target History'
on_press: sm.current = 'TargetHistory'; nav_drawer.set_state("close")
OneLineListItem:
text: 'About'
on_press: sm.current = 'AboutApp'; nav_drawer.set_state("close")
OneLineListItem:
text: 'Exit'
on_press: app.stop()
'''
)
self.history_table = MDDataTable(
column_data =[
("Target No.", dp(33)),
("x coordinate", dp(33)),
("y coordinate", dp(33)),
],
row_data=[("@startup", str(self.spawn_x), str(self.spawn_y))],
size_hint = (0.75, 0.5),
pos_hint = {"center_x": 0.5, "center_y": 0.45},
elevation = 10,
anchor_x = "center"
)
self.root.ids.sm.get_screen('TargetHistory').add_widget(self.history_table)
print('build ended') # remove line when done
def addtarget(self):
sr_no = self.history_table.row_data[-1][0]
if sr_no == "@startup":
sr_no = 0
else:
sr_no = int(sr_no)
self.history_table.row_data.insert(len(self.history_table.row_data), (str(sr_no + 1), str(self.root.ids.x_coordinate.text), str(self.root.ids.y_coordinate.text)))
self.root.ids.x_coordinate.text = ''
self.root.ids.y_coordinate.text = ''
def textcheck(self):
if not isfloat(self.root.ids.x_coordinate.text):
self.root.ids.x_coordinate.helper_text = 'Invalid Input'
elif not (float(self.root.ids.x_coordinate.text) <= 11.0) & (float(self.root.ids.x_coordinate.text) >= 0.0):
self.root.ids.x_coordinate.helper_text = 'Input from 0 to 11'
if not isfloat(self.root.ids.y_coordinate.text):
self.root.ids.y_coordinate.helper_text = 'Invalid Input'
elif not (float(self.root.ids.y_coordinate.text) <= 11.0) & (float(self.root.ids.y_coordinate.text) >= 0.0):
self.root.ids.y_coordinate.helper_text = 'Input from 0 to 11'
if (self.root.ids.x_coordinate.helper_text == '') & (self.root.ids.y_coordinate.helper_text == ''):
inp_x = float(self.root.ids.x_coordinate.text)
inp_y = float(self.root.ids.y_coordinate.text)
if ((round(inp_x,1) == round(self.input_x,1)) & (round(inp_y,1) == round(self.input_y,1))): # Need Improvement
print('\nW: turtle is already at this position. Please provide enough distance to move.\n')
# popup needed to show this message
else:
print(f'x coordinate: {self.root.ids.x_coordinate.text} & y coordinate: {self.root.ids.y_coordinate.text}')
self.input_x = inp_x
self.input_y = inp_y
# threading.Thread(target=self.posescreen).start()
self.root.ids.sm.current = 'TurtlePose'
# Clock.schedule_once(self.root.ids.sm.current, 0)
# Clock.create_trigger(self.root.ids.sm.current)
self.addtarget()
self.runturtle()
# def posescreen(self):
# self.root.ids.sm.current = 'TurtlePose'
def dataupdate(self):
self.root.ids.disp_x.text = str(self.pose_x)
self.root.ids.disp_y.text = str(self.pose_y)
self.root.ids.disp_dis.text = str(self.dist)
self.root.ids.x.text = str(self.pose_x)
self.root.ids.y.text = str(self.pose_y)
self.root.ids.theta.text = str(self.pose_theta)
def runturtle(self):
print('runturtle started') # remove line when done
pause(0.25)
self.root.ids.turtle_state.text = 'Running'
rclpy.spin_once(self.node)
self.dist, dirn = self.target_parameters(self.pose_x, self.pose_y, self.input_x, self.input_y)
# Clock.schedule_once(self.dataupdate, 0)
# self.update_data()
threading.Thread(target=self.dataupdate).start()
while not round(self.dist,4) == 0.0009:
print('turtle while loop started') # remove line when done
rclpy.spin_once(self.node)
rotation = dirn - self.pose_theta
if abs(rotation) > pi:
rotation = (rotation/abs(rotation))*(abs(rotation) - 2*pi)
self.msg_pub_operator.angular.z = 1.5*rotation
self.msg_pub_operator.linear.x = 1*self.dist
if abs(self.msg_pub_operator.linear.x)>1:
self.msg_pub_operator.linear.x = ((self.msg_pub_operator.linear.x)/abs(self.msg_pub_operator.linear.x))
self.dist, dirn = self.target_parameters(self.pose_x, self.pose_y, self.input_x, self.input_y)
# Clock.schedule_once(self.dataupdate, 0)
# self.update_data()
threading.Thread(target=self.dataupdate).start()
self.pub_operator.publish(self.msg_pub_operator)
print('turtle while loop ended') # remove line when done
self.msg_pub_operator.angular.z = 0.0
self.msg_pub_operator.linear.x = 0.0
self.pub_operator.publish(self.msg_pub_operator)
self.root.ids.turtle_state.text = 'Rest'
print('runturtle ended') # remove line when done
print('App class ended') # remove line when done
def main():
print('main started') # remove line when done
Gototarget().run()
print('main ended') # remove line when done
print('code ended') # remove line when done
It looks like you are running a loop:
while not round(self.dist,4) == 0.0009:
on the main thread. As long as this loop is holding the main thread, no updates will be shown in your GUI. It is that loop that should be run in another thread, and the code that modifies the GUI display can be triggered from within that loop using Clock.schedule_once()
.