How multiple run ros.init_node () in one python script? - python

I have written a program in python with ROS. It contains a GUI for "robot teleoperation", and in its MainWindow I added 3 widgets (rviz, joystick, button panel). When I start MainWindow I get the following error:
raise rospy.exceptions.ROSException("rospy.init_node() has already been called with different arguments: "+str(_init_node_args))
rospy.exceptions.ROSException: rospy.init_node() has already been
called with different arguments: ('teleop_twist_keyboard',
['MainWindow.py'], False, None, False, False).
Joystick.py and Button.py contain ros.init_node() function. In MainWindow I instantiate Joystick and Button class and add them to MainWindow. I need to call ros.init_node() several times to communicate with various nodes.
directory structure
main window example
code main window
import sys
import PyQt5
import threading
from Joystick import Joystick
from QWidget_rviz import Rviz
from BaseArmPosition import BaseArmPosition
class MainWindow(PyQt5.QtWidgets.QMainWindow):
def __init__(self,*args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.title = 'Robot teleoperation'
self.left = 10
self.top = 10
self.width = 1920
self.height = 1080
self.joy = Joystick(maxDistance=50,MinimumSize=100,EclipseX=-20,EclipseY=40)
self.rviz = Rviz()
#self.arm_position = BaseArmPosition()
self.initUI()
def initUI(self):
self.central_widget = PyQt5.QtWidgets.QWidget()
self.setCentralWidget(self.central_widget)
grid = PyQt5.QtWidgets.QGridLayout(self.centralWidget())
grid.addWidget(self.joy, 0, 2)
grid.addWidget(self.rviz, 0, 0)
#grid.addWidget(self.arm_position, 0, 1)
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
self.show()
app = PyQt5.QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
code buttons widget, joistick.py has the same
import PyQt5
import rospy
from std_msgs.msg import String
class BaseArmPosition(PyQt5.QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super(BaseArmPosition, self).__init__(*args, **kwargs)
self.initUI()
def initUI(self):
self.pushButton_moveInit = PyQt5.QtWidgets.QPushButton("moveInit",self)
self.pushButton_moveLeft = PyQt5.QtWidgets.QPushButton("moveLeft",self)
self.pushButton_moveRight = PyQt5.QtWidgets.QPushButton("moveRight",self)
layout = PyQt5.QtWidgets.QVBoxLayout(self)
layout.addWidget(self.pushButton_moveRight)
layout.addWidget(self.pushButton_moveLeft)
layout.addWidget(self.pushButton_moveInit)
self.pushButton_moveInit.clicked.connect(self.moveInit)
self.pushButton_moveLeft.clicked.connect(self.moveLeft)
self.pushButton_moveRight.clicked.connect(self.moveRight)
rospy.init_node("controller_ur")
self.pub_arm = rospy.Publisher("/controller_ur/order",String,queue_size=1)
def moveInit(self):
moveInit = "moveInit"
msg = String()
msg.data = moveInit
self.pub_arm.publish(moveInit)
def moveLeft(self):
moveInit = "moveLeft"
msg = String()
msg.data = moveInit
self.pub_arm.publish(moveInit)
def moveRight(self):
moveInit = "moveRight"
msg = String()
msg.data = moveInit
self.pub_arm.publish(moveInit)

A ROS node can only be initialized once in your program. You should centralize the initialization of the node at the beginning of the main script, and the rest of imported modules should not try to initialize a new node, as that is not allowed.
If what you want is the different sub-modules to deal with different data, then you should just create separate topics within the same node.

Put this somewhere towards the top of your script before import statements.
import os
os.system("rosrun my_package_name my_node_script.py")

Related

finplot candlestick chart does not render on launch when rendered as a widget

I have placed finplot candlestick chart as a widget. When launched, it comes up black on first render of app. If I zoom out, it starts to show up in the maximum zoomed state. How can I launch it in a state so that all candles that are inside the chart is displayed. Following is my entire code.
import sys
import finplot as fplt
import pandas as pd
import pyqtgraph as pg
import requests
from PyQt5.QtWidgets import *
# Creating the main window
class App(QMainWindow):
def __init__(self):
super().__init__()
self.title = 'PyQt5 - QTabWidget'
self.left = 0
self.top = 0
self.width = 1200
self.height = 800
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
self.tab_widget = MyTabWidget(self)
self.setCentralWidget(self.tab_widget)
self.show()
# Creating tab widgets
class MyTabWidget(QWidget):
def __init__(self, parent):
super(QWidget, self).__init__(parent)
self.layout = QVBoxLayout(self)
# Initialize tab screen
self.tabs = QTabWidget()
self.tabs.setTabPosition(QTabWidget.East)
self.tabs.setMovable(True)
self.tab1 = QWidget()
self.tabs.resize(600, 400)
# Add tabs
self.tabs.addTab(self.tab1, "tab1")
self.tab1.layout = QVBoxLayout(self)
self.tab1.label = QLabel("USDT-BTC")
self.tab1.fplt_widget = pg.PlotWidget(plotItem=fplt.create_plot_widget(self.window()))
self.tab1.btn = QPushButton("Press me")
# pull some data
symbol = 'USDT-BTC'
url = 'https://bittrex.com/Api/v2.0/pub/market/GetTicks?marketName=%s&tickInterval=fiveMin' % symbol
data = requests.get(url).json()
# format it in pandas
df = pd.DataFrame(data['result'])
df = df.rename(columns={'T':'time', 'O':'open', 'C':'close', 'H':'high', 'L':'low', 'V':'volume'})
df = df.astype({'time':'datetime64[ns]'})
candles = df[['time','open','close','high','low']]
fplt.candlestick_ochl(candles)
self.tab1.layout.addWidget(self.tab1.label)
self.tab1.layout.addWidget(self.tab1.fplt_widget)
self.tab1.layout.addWidget(self.tab1.btn)
self.tab1.setLayout(self.tab1.layout)
# Add tabs to widget
self.layout.addWidget(self.tabs)
self.setLayout(self.layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
Here is my app when launched:
Here it is when one scroll zoomed out
I want to see it as follows as soon as it launches
Thank you for posting full details of your problem.
Everything is almost there. You just need to add two things:
In your App.__init add one of the following lines just before calling self.show():
fplt.show(qt_exec=False)
or just
fplt.refresh()
Either choice will work because fplt.refresh() is called in either case. This autozooms your data plot, among other good things.
For the refresh call to work in either of the above choices, the window instance that you passed to fplt.create_plot_widget needs an axs attribute containing a list of your fplt widgets that you created. In your case, you wrap your fplt widget in a pg.PlotWidget, so the required widget reference becomes self.tab1.fplt_widget.plotItem (note the .plotItem there). Long story short: in MyTabWidget.__init__ insert this line before adding your widget to self.tab1.layout:
self.window().axs = [self.tab1.fplt_widget.plotItem]
Running your posted code with the above changes gives me the initial plot view that you desire.

How to show a widget on button click of another widget

My question is simple, I am trying to open a child window within the main window. I took help of all of the answers so this question is not a duplicate. My child window comes for some time and then disappears automatically.
import random
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5 import QtWidgets, uic
class SubWindow(QWidget):
def __init__(self, parent = None):
super(SubWindow, self).__init__(parent)
label = QLabel("Sub Window", self)
label.setGeometry(0, 0, 20, 10)
self.show()
class pro(QWidget):
def __init__(self, parent=None):
super(pro, self).__init__(parent)
self.acceptDrops()
self.x = 200
self.y = 200
self.width = 800
self.length = 800
self.setGeometry(self.x, self.y, self.width, self.length)
self.setWindowTitle("PA")
label = QLabel(self)
pixmap = QPixmap('p_bg.png')
label.setPixmap(pixmap)
label.setGeometry(0, 0, 1100, 1000)
self.initgui()
def register_system(self):
opener = SubWindow()
opener.show()
def initgui(self):
self.btn_log = QPushButton(self)
self.btn_log.setGeometry(440, 250, 150, 30)
self.btn_log.setText(" Login ")
self.btn_log.adjustSize()
self.btn_sign = QPushButton(self)
self.btn_sign.setGeometry(440, 300, 150, 30)
self.btn_sign.setText(" Register ")
self.btn_sign.adjustSize()
self.btn_sign.clicked.connect(self.register_system)
self.welcome = QLabel("Arial font", self)
self.welcome.setText("Welcome")
self.welcome.setGeometry(410, 100, 200, 30)
self.welcome.setStyleSheet("background-color:transparent;")
self.welcome.setFont(QFont("Arial", 30))
self.show()
def window():
apk = QApplication(sys.argv)
win = pro()
win.show()
sys.exit(apk.exec_())
window()
I took help to make a child window from here:https://www.codersarts.com/post/multiple-windows-in-pyqt5-codersarts , I used type 2 resource in my case.
The reason probably is that the variable opener has scope within the register_system method only; it is getting deleted after the method exits, you can modify the code either to create the variable as an attribute to the class object using self and then show the widget like this:
def register_system(self):
self.opener = SubWindow()
self.opener.show()
Or, you can just create a variable opener at global scope, then use the global variable to assign and show the widget:
# imports
opener = None
class SubWindow(QWidget):
def __init__(self, parent = None):
#Rest of the code
class pro(QWidget):
def __init__(self, parent=None):
# Rest of the code
def register_system(self):
global opener
opener = SubWindow()
opener.show()
# Rest of the code
On a side note, you should always use layout for the widgets in PyQt application, you can take a look at Layout Management

PyQt5 removing button

In the list_widget I have added a add button I also want to add a remove button which asks which item you wants to remove and remove the chosen item. I was trying it to do but I didn't had any idea to do so .Also, please explain the solution I am a beginner with pyqt5 or I'd like to say absolute beginner.
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication,QMainWindow,
QListWidget, QListWidgetItem
import sys
class MyWindow(QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
self.x = 200
self.y = 200
self.width = 500
self.length = 500
self.setGeometry(self.x, self.y, self.width,
self.length)
self.setWindowTitle("Stock managment")
self.iniTUI()
def iniTUI(self):
# buttons
self.b1 = QtWidgets.QPushButton(self)
self.b1.setText("+")
self.b1.move(450, 100)
self.b1.resize(50, 25)
self.b1.clicked.connect(self.take_inputs)
# This is the button I want to define.
self.btn_minus = QtWidgets.QPushButton(self)
self.btn_minus.setText("-")
self.btn_minus.move(0, 100)
self.btn_minus.resize(50, 25)
# list
self.list_widget = QListWidget(self)
self.list_widget.setGeometry(120, 100, 250, 300)
self.item1 = QListWidgetItem("A")
self.item2 = QListWidgetItem("B")
self.item3 = QListWidgetItem("C")
self.list_widget.addItem(self.item1)
self.list_widget.addItem(self.item2)
self.list_widget.addItem(self.item3)
self.list_widget.setCurrentItem(self.item2)
def take_inputs(self):
self.name, self.done1 =
QtWidgets.QInputDialog.getText(
self, 'Add Item to List', 'Enter The Item you want
in
the list:')
self.roll, self.done2 = QtWidgets.QInputDialog.getInt(
self, f'Quantity of {str(self.name)}', f'Enter
Quantity of {str(self.name)}:')
if self.done1 and self.done2:
self.item4 = QListWidgetItem(f"{str(self.name)}
Quantity{self.roll}")
self.list_widget.addItem(self.item4)
self.list_widget.setCurrentItem(self.item4)
def clicked(self):
self.label.setText("You clicked the button")
self.update()
def update(self):
self.label.adjustSize()
def clicked():
print("meow")
def window():
apk = QApplication(sys.argv)
win = MyWindow()
win.show()
sys.exit(apk.exec_())
window()
The core issue here is the lack of separation of the view and the data. This makes it very hard to reason about how to work with graphical elements. You will almost certainly want to follow the Model View Controller design paradigm https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
which offers a systematic way to handle this separation.
Once you do so, it immediately becomes very straight forward how to proceed with the question: You essentially just have a list, and you either want to add a thing to this list, or remove one based on a selection.
I include an example here which happens to use the built-in classes QStringListModel and QListView in Qt5, but it is simple to write your own more specialized widgets and models. They all just use a simple signal to emit to the view that it needs to refresh the active information.
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
class StuffViewer(QMainWindow):
def __init__(self, model):
super().__init__()
self.setWindowTitle("Stock managment")
# 1: Use layouts.
hbox = QHBoxLayout()
widget = QWidget()
widget.setLayout(hbox)
self.setCentralWidget(widget)
# 2: Don't needlessly store things in "self"
vbox = QVBoxLayout()
add = QPushButton("+")
add.clicked.connect(self.add_new_stuff)
vbox.addWidget(add)
sub = QPushButton("-")
sub.clicked.connect(self.remove_selected_stuff)
vbox.addWidget(sub)
vbox.addStretch(1)
hbox.addLayout(vbox)
# 3: Separate the view of the data from the data itself. Use Model-View-Controller design to achieve this.
self.model = model
self.stuffview = QListView()
self.stuffview.setModel(self.model)
hbox.addWidget(self.stuffview)
def add_new_stuff(self):
new_stuff, success = QInputDialog.getText(self, 'Add stuff', 'Enter new stuff you want')
if success:
self.stuff.setStringList(self.stuff.stringList() + [new_stuff])
def remove_selected_stuff(self):
index = self.stuffview.currentIndex()
all_stuff = self.stuff.stringList()
del all_stuff[index.column()]
self.stuff.setStringList(all_stuff)
def window():
apk = QApplication(sys.argv)
# Data is clearly separated:
# 4: Never enumerate variables! Use lists!
stuff = QStringListModel(["Foo", "Bar", "Baz"])
# The graphical components is just how you interface with the data with the user!
win = StuffViewer(stuff)
win.show()
sys.exit(apk.exec_())
window()

handling multiple QWidget

I am building a parser with a main QWidget that takes a file input, and open a new QWidget and display a table with parsed data.
However, when I selected a new file from the main QWidget, it is replacing the second QWidget instead of instantiating another QWidget.
Also, I want to be able to close the main QWidget and close all QWidgets and exit the program. It is not doing that right now.
Full Code: main.py
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import QColor
from dbparser import DBParser
import xlsxwriter
class PopUp(QWidget):
def __init__(self, tablename, fields, rows):
super(PopUp, self).__init__()
#Window Property
self.title = tablename
self.left = 300
self.top = 250
self.width = 1000
self.height = 750
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
...
class App(QWidget):
def __init__(self):
super().__init__()
self.title = 'CMSDBParser'
self.left = 150
self.top = 150
self.width = 500
self.height = 200
self.file = ""
self.filepath = QLineEdit()
self.bigEditor = QTextEdit()
self.browse_btn = QPushButton('Browse...')
self.parse_btn = QPushButton('Parse...')
self.initUI()
...
def parse(self):
if self.file:
try:
parser = DBParser(self.file)
dbcheck = parser.isDB()
if dbcheck == True:
tablename, fields, rows = parser.getData()
self.popup = PopUp(tablename, fields, rows)
self.popup.show()
else:
QMessageBox.information(self, 'Warning', 'Right format but not CMS DB File')
except:
QMessageBox.critical(self, 'Error', "Invalid File Selected \nMust be .txt or .log format")
self.parse_btn.setEnabled(False)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
The problem is that you are using the same attribute: self.popup and if you create another widget it will replace it eliminating the previous window, in this case the solution is to create a list that stores the PopUp, on the other hand it is recommended to enable the attribute Qt::WA_DeleteOnClose making that when the PopUp is closed it is eliminated from the memory so that no unnecessary memory will be kept. For the case of your second requirement, an option is to overwrite the closeEvent() method and close the PopUp, but to do this you must verify if it exists since when an item is deleted from memory it does not imply that you delete its reference in the list. use sip.isdeleted()
import sip
# others imports
class App(QWidget):
def __init__(self):
super().__init__()
# ...
self.popups = [] # <---
def parse(self):
if self.file:
try:
parser = DBParser(self.file)
if parser.isDB():
tablename, fields, rows = parser.getData()
popup = PopUp(tablename, fields, rows) # <---
popup.setAttribute(Qt.WA_DeleteOnClose) # <---
popup.show() # <---
self.popups.append(popup) # <---
else:
QMessageBox.information(self, 'Warning', 'Right format but not CMS DB File')
except:
QMessageBox.critical(self, 'Error', "Invalid File Selected \nMust be .txt or .log format")
self.parse_btn.setEnabled(False)
def closeEvent(self, event):
for popup in self.popups: # <---
if not sip.isdeleted(popup): # <---
w.close() # <---
super(App, self).closeEvent(event)

Multithreading with PySide : is my structure viable?

I'm running into a very strange error while using QThread in PySide. As I run my code, when I click on the button that is supposed to launch the thread and send Signal, I get :
AttributeError: 'builtin_function_or_method' object has no attribute 'displayText'
at line :
self.displayMessage.connect(self.window.displayText)
NB : displayText is a method that I defined in my MainWindow(QWidget) class, while displayMessage is the signal to be emitted.
This error seems, according to the Internet, to occur in various situations, and I couldn't find one that suits my case yet. Therefore I have 2 questions :
Have you guys ever met this error before in a similar situation, and could you (if yes) give me some tips ?
Am I doing it right ? I can't post my code directly down here, so I created a short verifiable example in which I used the same process. Unfortunately, it seems to work perfectly. Please tell me if at least my construction is correct.
Thank you very much.
EDIT : I eventually figured out that I have forgotten a self somewhere in my big code, but the actual error was hidden to me behind this one that I didn't know. I'm still interested in whether or not this code is reliable, and am fully open to suggestions of improvement.
Without thread
When you click "GO !" and try to move the window, you can see that it freezes for a second.
#!/usr/bin/env python
# -*- encoding : utf-8 -*-
import sys
import time
from PySide.QtCore import *
from PySide.QtGui import *
class MainWindow(QWidget):
def __init__(self, qt_app):
QWidget.__init__(self)
worker = Worker(self)
self.qt_app = qt_app
self.setGeometry(100, 100, 220, 40)
self.infoPanel = QTextEdit(self)
self.infoPanel.setReadOnly(True)
self.goButton = QPushButton('GO !', self)
self.goButton.clicked.connect(lambda: worker.displayOnWindow())
self.layout = QVBoxLayout()
self.layout.addWidget(self.infoPanel)
self.layout.addWidget(self.goButton)
self.setLayout(self.layout)
def launch(self):
self.show()
self.qt_app.exec_()
class Worker():
def __init__(self, window):
self.counter = 0
self.window = window
def displayOnWindow(self):
time.sleep(1)
self.window.infoPanel.append(str(self.counter))
self.counter += 1
if __name__=='__main__':
qt_app = QApplication(sys.argv)
mw = MainWindow(qt_app)
mw.launch()
With thread
Now you can move the window without trouble, since the sleeping thread is not the one that displays the window. I've written a sub-class for my thread because in the original code there are some functions that will be called by several buttons.
#!/usr/bin/env python
# -*- encoding : utf-8 -*-
import sys
import time
from PySide.QtCore import *
from PySide.QtGui import *
class MainWindow(QWidget):
def __init__(self, qt_app):
QWidget.__init__(self)
worker = SubWorker(self)
self.qt_app = qt_app
self.setGeometry(100, 100, 220, 40)
self.infoPanel = QTextEdit(self)
self.infoPanel.setReadOnly(True)
self.goButton = QPushButton('GO !', self)
self.goButton.clicked.connect(lambda: worker.start())
self.layout = QVBoxLayout()
self.layout.addWidget(self.infoPanel)
self.layout.addWidget(self.goButton)
self.setLayout(self.layout)
def launch(self):
self.show()
self.qt_app.exec_()
#Slot(int)
def displayOnWindow(self, i):
self.infoPanel.append(str(i))
class Worker(QThread):
displayMessage = Signal(int)
def __init__(self, window):
QThread.__init__(self)
self.counter = 0
self.window = window
self.displayMessage.connect(self.window.displayOnWindow)
def run(self, *args, **kwargs):
return QThread.run(self, *args, **kwargs)
class SubWorker(Worker):
def __init__(self, window):
Worker.__init__(self, window)
def run(self):
time.sleep(1)
self.displayMessage.emit(self.counter)
self.counter += 1
if __name__=='__main__':
qt_app = QApplication(sys.argv)
mw = MainWindow(qt_app)
mw.launch()

Categories