PyQT4/5 Application

Hi, i run this project, but when i try to load xbee modules with the app, can’t load any xbee. I use PyChram, iwith Python 3.8. and
Any help?

Hi, so given the very minimal information provided, this seems like an issue with xbee rather than with the Python interpreter, I’d suggest you contact the support channels of that project instead, and provide them detailed and specific information regarding exactly what error message and full traceback you are seeing, and/or a detailed description of the behavior you are seeing and how it differs from what you expect.

Also, the project you linked has been unmaintained for 3-4 years, and relies on PyQt4, which has been EOL for a very long time. Shameless plug, but using an abstraction layer like QtPy (shameless plug, I’m one of the maintainers) will help ensure your project will seamlessly work across different versions of PyQt and Pyside and their underlying Qt bindings with minimal effort.

Best of luck!

Hi. Thanks for reply
The Xbee work fine, i’ve test them on PC, RPI and with XCTU.
So, the problem is in the App.
I try contact with the creator, but no reply.

There is no ‘‘behavior’’ at all, after i load the app and hit the ‘‘Compound’’ button, the app do nothing.

I update the code, so PyQt4 to become PyQt5, and the GUI work fine, it seems just the xbee settings has problems.

I didn’t know about QtPy, but i will try to use it in the future.

Just to keep in mind, it hasn’t been touched in five years, so if you want to continue to use it, I would plan to maintain it on your own. As a start, considering it is already using the logging module and displaying the output in the main window, you could add a bunch of logging calls after whatever the compound button triggers to see what is going on, and debug from there. But as mentioned, this isn’t really much of a Python question, so unfortunately I’m not sure how much anyone here can do, sorry. Cheers!

Well, i’m a beginner, so i don’t know how to ‘‘add a bunch of logging calls’’.
The

XbeeCommands.py and XbeeConnect.py files don’t give any superficial errors. If we assume, that those two files have some error, how to further test (with another tools/methods) them for errors?

Sorry for the questions, but i’m new to programming.

Ah, okay—sorry, I shouldn’t have assumed! In any case, debugging is an important part of learning how to program; you’ll often be spending much if not most of your time doing so, and it is a really useful skill to have.

In that case, lets go over some debugging methods. The most basic is to just use the print() function, either to print the values of the objects you’re interested in to see if they match what you’d expect, and to see what paths the code took. In this case, prints will usually go to the console window in which you started the program, but with GUI programs like this one there may not be a console window, or it may be redirected, so it may not always work. Additionally, it means you have to add print statements every time you want to debug a section a code, and remove them when you’re done, which gets tedious and can lead to mistakes.

Using Python’s logging module (import logging) is usually a much better approach for anything beyond simple scripts, and particularly for applications like this one, as it allows you to dynamically change the detail level of the logging without having to add/remove statements, easily redirect it to a file, your OS’s logging system or other destinations, filter messages, include extra data with them, easily log errors and exceptions, and much more. The one downside is it takes a bit more setup, but fortunately, all that is already done for you by the original author. To write debug messages to the log, simply call logging.debug() just as you would print() (you can see it used a number of places this way in MainForm.py, including the Logger initialized call you see above). See the official basic tutorial for more information, which also links to more advanced guides and the API docs.

In addition, while I’m not sure how useful it will be here for this being an interactive GUI application, there’s also using a debugger to step line by line through the code and see what’s happening. Python includes PDB, which you can trigger with breakpoint(), and use the commands mentioned at that link to step through your code.

To get you started, you’ll want to look at MainForm.py where most of the work happens. Typically, a good place to start is searching the UI text (compund) of the button or field you’re trying to edit, and seeing what its connected to. However, I can’t seem to find that anywhere in the code you linked, so either its translated from what appears to be Russian, its in a different version of the application, or it is created by something other than the project linked. You can, however, search the actual code of the package you have downloaded/installed, and go from there.

I will try your advice, at least as far as I can understand the details.

In the meantime, is there an option to see/edit python code, like Blocks ?
blocks

I’m not aware of a dev tool that holds your hand quite that much, but any good IDE has syntax highlighting, an outline view, interactive help and code completion, which replicates most of the same benefits. Spyder is a particularly beginner friendly open source Python IDE (not only specifically for Python, but written in Python itself using PyQt5 via QtPy, much like this GUI you’ve linked) that is oriented toward scientific, engineering and data analysis applications. Of course, keep in mind that I’m a little biased, since I’m one of the Spyder core devs :grinning_face_with_smiling_eyes:

The ''import logging''
exist in the ''MainForm.py'', but nothing came up (except ''DEBUG - Logger initialized'').

I try PDB, but can’t install ''mymodues'', so i have error.

Can you give me some addition tips ?

I’m not really clear what you’re saying here, sorry. Did you actually try injecting logger.debug or logger.info, etc calls into the code, including in proximity to the original one, and they didn’t display anything? If not, its possible either the logger is reconfigured or those parts of the code are simply not getting reached, which also tells you something. Perhaps the application is even hanging?

Beyond this, given this is a third party application that has seen virtually no activity or even obvious use for around 5 years, I’m not sure how much else I can help you beyond the copious information I’ve already provided and research I’ve done for you into this, unless I debug it myself. If you still can’t figure it out and can’t reach the original author, I’d suggest looking into alternatives or writing a simple client yourself. I’m not sure what else there is to do, sorry.

Best of luck!

Hi, i’m new and still experimenting.
So, i type import logger.debug (in mainform.py) , and get error ‘‘ No module logger.debu g’’.
I try to install logger.debug (separately, via interpreter), but still have the same error.

How to fix this problem, and what to do onward?

Hey, I suggest re-reading the basic tutorial I linked.

logging is a standard library module, while logging.debug is a function in the module, so you don’t need to import the function itself. Just call it with logging.debug("Your message here"), exactly like it was already called in the code. In fact, I suggest just adding another logging.debug() on the very next line to the one that’s already there (that says logger initialized) and printing your own message, and then slowly iterating from there, so you can build up a basic understanding of how things work without running into issues right away.

This could be a great learning experience (my experience as a new Python programmer fixing bugs and helping with the development of the PyQt5/QtPy-based Spyder IDE was how I learned a lot of the basic skills and knowledge I have today), and its great that you’re challenging yourself. Based on what you’ve shared so far, I’m concerned it might be a little bit too overwhelming at first for somewhat at your level, especially without any help from the author (as I had from the other Spyder developers). So if you’re not able to solve it in the end, hopefully it is still valuable as a learning experience and you don’t feel too frustrated about it! But hope it works out!

Hi, thanks for the reply.

I did what you told me, and I have this in the app (photo).

I don’t know if it matters, but the messages appear with the start of the app (before i hit the Compound button).

So, i don’t know what to do next ?

Yes, i hope to build learning experience with this app. And i hope to build it properly, because it is important for me.
Xbee-gui error 2

Good, so at least that works.

Right, as expected, because the logger is set up with application startup, not on pushing the button. You want to move your logging calls to where they first when you push the button, but at least skimming the code, it isn’t obvious where that is (because I can’t find the UI text string of the button, perhaps because of internationalization issues or because the version on GitHub does not match what you have locally).

Now that you have the very basics down, I guess as I implied above you now want to trace the code and figure out what callable the button is connected to (in Qt parlance, it sends a Signal to a Slot), and put your logging calls there to figure out what is (or isn’t) going on, and debug from there. I can’t really give you step by step instructions, as figuring out exactly what you need to do is an intrinsic part of the debugging process (and I can’t seem to find the button at all in the version of the code you linked), but being able to figure things out on your own, bit by bit, by tracing the code, injecting logging calls and iterating from there is probably the single most critical skill involved in debugging a problem on an application whose codebase you’re not yet fully familiar with (or even your own).

Hi, thanks for the reply.

I will follow your advice again.

The ‘‘Compound’’ button exist on several places :


    """ Connection parameters, for the 1st tab"""
    def parameter_connecting(self):
        com_lbl = QtWidgets.QLabel('COM')
        com_list = QtWidgets.QComboBox()
        com_list.setFixedWidth(80)
        com_list.addItems(["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16","17"])
        self.grid.addWidget(com_list, 1, 1)
        speed_lbl = QtWidgets.QLabel('Speed')
        speed_list = QtWidgets.QComboBox()
        speed_list.setFixedWidth(80)
        speed_list.addItems(["9600", "115200"])
        self.grid.addWidget(speed_list, 2, 1)
        data_bits_lbl = QtWidgets.QLabel('Data bits')
        data_bits_list = QtWidgets.QComboBox()
        data_bits_list.setFixedWidth(80)
        data_bits_list.addItems(["5", "6", "7", "8"])
        self.grid.addWidget(data_bits_list, 3, 1)
        stop_bit_lbl = QtWidgets.QLabel('Stop bit')
        stop_bit_list = QtWidgets.QComboBox()
        stop_bit_list.setFixedWidth(80)
        stop_bit_list.addItems(["1", "2"])
        self.grid.addWidget(stop_bit_list, 4, 1)
        parity_lbl = QtWidgets.QLabel('Parity')
        parity_list = QtWidgets.QComboBox()
        parity_list.setFixedWidth(80)
        parity_list.addItems(["None", "Odd", "Even", "Mark", "Space"])
        self.grid.addWidget(parity_list, 5, 1)
        flow_control_lbl = QtWidgets.QLabel('Flow Control')
        flow_control_list = QtWidgets.QComboBox()
        flow_control_list.setFixedWidth(80)
        flow_control_list.addItems(["None", "XOnXOff", "Request To Send", "Request To SendXOnXOff"])
        self.grid.addWidget(flow_control_list, 6, 1)
        self.connecting_btn = QtWidgets.QPushButton(u"Compound")
        self.grid.addWidget(self.connecting_btn, 7, 0)
        self.grid.addWidget(com_lbl, 1, 0)
        self.grid.addWidget(speed_lbl, 2, 0)
        self.grid.addWidget(data_bits_lbl, 3, 0)
        self.grid.addWidget(stop_bit_lbl, 4, 0)
        self.grid.addWidget(parity_lbl, 5, 0)
        self.grid.addWidget(flow_control_lbl, 6, 0)
        com_lbl.resize(500, com_lbl.height())
        self.connPrefFiels = [com_list, speed_list, data_bits_list, stop_bit_list, parity_list, flow_control_list]

    """ Management tab """

    def tab_control(self):
        tab2_layout = QtWidgets.QHBoxLayout(self.tab2)
        send_commands = QtWidgets.QGroupBox(u'Sending a command')
        list_commands = QtWidgets.QGroupBox(u'Team selection')
        tab2_layout.addWidget(send_commands)
        tab2_layout.addWidget(list_commands, QtCore.Qt.AlignLeft)
        send_commands_layout = QtWidgets.QGridLayout(send_commands)
        self.list_commands_layout = QtWidgets.QHBoxLayout(list_commands)
        self.list_all_commands()
        type_commands_lbl = QtWidgets.QLabel(u'Team type')
        send_commands_layout.addWidget(type_commands_lbl, 1, 0)
        list_type_commands = QtWidgets.QComboBox()
        list_type_commands.setFixedWidth(80)
        list_type_commands.addItems(["AT", "Remote AT"])
        send_commands_layout.addWidget(list_type_commands, 1, 1)
        command_lbl = QtWidgets.QLabel(u'Command')
        send_commands_layout.addWidget(command_lbl, 2, 0)
        self.comm_edit = QtWidgets.QLineEdit()
        self.comm_edit.setFixedWidth(80)

        """ Command completion """

        model = QtCore.QStringListModel()
        model.setStringList(commands)
        completer = QtWidgets.QCompleter()
        completer.setCaseSensitivity(0)
        completer.setModel(model)
        self.comm_edit.setCompleter(completer)
        send_commands_layout.addWidget(self.comm_edit, 2, 1)
        parameter_lbl = QtWidgets.QLabel(u'Options')
        send_commands_layout.addWidget(parameter_lbl, 3, 0)
        comm_parameter_edit = QtWidgets.QLineEdit()
        comm_parameter_edit.setFixedWidth(80)
        send_commands_layout.addWidget(comm_parameter_edit, 3, 1)
        send_command_btn = QtWidgets.QPushButton(u'send')
        send_commands_layout.addWidget(send_command_btn, 4, 0)
        send_command_btn.clicked.connect(self.send_btn_clicked)

    """ List of available commands """

    def list_all_commands(self):
        self.commands_list_model = QtGui.QStandardItemModel()
        self.view = QtWidgets.QTreeView()
        self.view.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
        self.view.setModel(self.commands_list_model)
        self.commands_list_model.setHorizontalHeaderLabels([u'Command'])
        parent = self.commands_list_model.invisibleRootItem()
        for c in XbeeCommands.ALL_CLASSES:
            class_name_item = QtGui.QStandardItem(c.__name__[1:])
            parent.appendRow(class_name_item)
            for c_item in [QtGui.QStandardItem(c) for c in dir(c) if not c.startswith("__")]:
                class_name_item.appendRow(c_item)
        self.list_commands_layout.addWidget(self.view)

        def on_item_clicked(index):
            command_str = str(index.data().toString())
            if index.parent().column() == 0:
                self.comm_edit.setText(commands_dict[command_str].command)

        self.view.clicked.connect(on_item_clicked)

    """ Logging """

    def optios_log(self):
        self.logWidget = QTextEditLogger(self)
        self.logWidget.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', "%Y-%m-%d %H:%M:%S"))
        logging.getLogger().addHandler(self.logWidget)
        logging.getLogger().setLevel(logging.DEBUG)

        """Saving logs to a file"""

        name_file_log = logging.FileHandler('test.log')
        name_file_log.setLevel(logging.DEBUG)
        ch = logging.StreamHandler()
        ch.setLevel(logging.ERROR)
        formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
        ch.setFormatter(formatter)
        name_file_log.setFormatter(formatter)
        logging.getLogger().addHandler(ch)
        logging.getLogger().addHandler(name_file_log)
        self.centralWidgetLayout.addWidget(self.logWidget.widget)
        logging.debug("Logger initialized")
        logging.debug("Your message here")

If you want i can send you google grive link to the project ?

I only see it added one place—here. You can see the button widget is added the layout, but at least from skipping the code provided, I don’t see where an action (callback) is hooked up to the button. So there’s three possibilities:

  • The command is hooked up using the on_item_clicked callable operating on the whole view, which means you have to find where commands_dict is defined and figure out what command it’s calling.
  • Something later adds the action callback/hooks up the on_item_clicked signal (not 100% sure how it works in Qt/PyQt4) to the button, which you can trace as the .connecting_btn attribute of whatever class the parameter_connection method is a member of.
  • Its never hooked up at all, which would explain why its not doing anything.

I suggest you explore each of those routes, in order, using your logging calls and code tracing, and go from there.

Unfortunately, as I’ve stated, I am not able to hold your hand through each and every atomic step of this. You are effectively debugging and rewriting a GUI application that has been unmaintained for five years and uses an obsolete version of Qt. This is a major challenge for someone even experienced at Python, and while I’m all for pushing yourself as a learning opportunity, if it is overwhelming and frustrating enough it might not be the best thing right now. There’s zero shame in taking a look at other less complicated projects first and maybe coming back to this later. But if you do decide to continue, I implore you to really try to break things down step by step and incrementally figure it out, using all the resources at your disposal—your knowledge, logging, experimentation, the docs, Google, etc. That way, even if you don’t manage to actually fix the problem, you will still have learned a lot about how to go about this task yourself, and what works and what doesn’t.

Best of luck!

Hi, thanks for the reply.

I will follow your advice again.

Sorry to bother you with every atomic step, but don’t know where to ask (i surf everywhere for any help).

I’ve try similar projects, but they aither have ‘‘dead end errors’’, or don’t have enough functionality. This is the last project of this category, that i manage to find. I don’t have enough experience, to create that type of project from zero.

I know it is stupid, but let me ask again : Can i send you the edited project via Google drive ?

• The command is hooked up using the on_item_clicked callable operating on the whole view, which means you have to find where commands_dict is defined and figure out what command it’s calling.

From MainForm.py

commands = []
commands_dict = {}
for i in XbeeCommands.ALL_CLASSES:
    for command in [command for command in dir(i) if not command.startswith("__")]:
        commands.append(command)
        commands_dict[command] = i.__dict__.get(command)


""" List of available commands """

def list_all_commands(self):
    self.commands_list_model = QtGui.QStandardItemModel()
    self.view = QtWidgets.QTreeView()
    self.view.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
    self.view.setModel(self.commands_list_model)
    self.commands_list_model.setHorizontalHeaderLabels([u'Command'])
    parent = self.commands_list_model.invisibleRootItem()
    for c in XbeeCommands.ALL_CLASSES:
        class_name_item = QtGui.QStandardItem(c.__name__[1:])
        parent.appendRow(class_name_item)
        for c_item in [QtGui.QStandardItem(c) for c in dir(c) if not c.startswith("__")]:
            class_name_item.appendRow(c_item)
    self.list_commands_layout.addWidget(self.view)

    def on_item_clicked(index):
        command_str = str(index.data().toString())
        if index.parent().column() == 0:
            self.comm_edit.setText(commands_dict[command_str].command)

    self.view.clicked.connect(on_item_clicked)

""" Network structure, all connected devices """

def tab_network_structure(self):
    tab3_layout = QtWidgets.QHBoxLayout(self.tab3)
    #Test option for displaying images for each type of device
    l1 = QtWidgets.QLabel()
    l1.setPixmap(QtGui.QPixmap("router.png"))
    tab3_layout.addWidget(l1)

    #search_devices = QtGui.QPushButton(u"Network scan")
    #tab3_layout.addWidget(search_devices)
    #self.search_list = QtGui.QStandardItemModel()
    #self.search_view = QtGui.QTreeView()
    #self.search_view.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
    #self.search_view.setModel(self.search_list)
    #self.search_list.setHorizontalHeaderLabels(['MY', 'SH', 'SL', 'ID', 'Type'])
    #tab3_layout.addWidget(self.search_view)

    def on_item_clicked(index):
        command_str = str(index.data().toString())
        if index.parent().column() == 0:
            self.comm_edit.setText(commands_dict[command_str].command)

    self.view.clicked.connect(on_item_clicked)

    self.tabWidget.currentChanged.connect(self.hide_log)

From XbeeConnect.py


commands = []
commands_short = []

for i in XbeeCommands.ALL_CLASSES:
    for command in [command for command in dir(i) if not command.startswith("__")]:
        c = i.__dict__.get(command)
        commands.append(c)
        commands_short.append(c.command)

commands_dict = dict(zip(commands_short, commands))

• Something later adds the action callback/hooks up the on_item_clicked signal (not 100% sure how it works in Qt/PyQt4) to the button, which you can trace as the .connecting_btn attribute of whatever class the parameter_connection method is a member of.

From MainForm.py


""" Connection parameters, for the 1st tab"""
def parameter_connecting(self):
    com_lbl = QtWidgets.QLabel('COM')
    com_list = QtWidgets.QComboBox()
    com_list.setFixedWidth(80)
    com_list.addItems(["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16","17"])
    self.grid.addWidget(com_list, 1, 1)
    speed_lbl = QtWidgets.QLabel('Speed')
    speed_list = QtWidgets.QComboBox()
    speed_list.setFixedWidth(80)
    speed_list.addItems(["9600", "115200"])
    self.grid.addWidget(speed_list, 2, 1)
    data_bits_lbl = QtWidgets.QLabel('Data bits')
    data_bits_list = QtWidgets.QComboBox()
    data_bits_list.setFixedWidth(80)
    data_bits_list.addItems(["5", "6", "7", "8"])
    self.grid.addWidget(data_bits_list, 3, 1)
    stop_bit_lbl = QtWidgets.QLabel('Stop bit')
    stop_bit_list = QtWidgets.QComboBox()
    stop_bit_list.setFixedWidth(80)
    stop_bit_list.addItems(["1", "2"])
    self.grid.addWidget(stop_bit_list, 4, 1)
    parity_lbl = QtWidgets.QLabel('Parity')
    parity_list = QtWidgets.QComboBox()
    parity_list.setFixedWidth(80)
    parity_list.addItems(["None", "Odd", "Even", "Mark", "Space"])
    self.grid.addWidget(parity_list, 5, 1)
    flow_control_lbl = QtWidgets.QLabel('Flow Control')
    flow_control_list = QtWidgets.QComboBox()
    flow_control_list.setFixedWidth(80)
    flow_control_list.addItems(["None", "XOnXOff", "Request To Send", "Request To SendXOnXOff"])
    self.grid.addWidget(flow_control_list, 6, 1)
    self.connecting_btn = QtWidgets.QPushButton(u"Compound")
    self.grid.addWidget(self.connecting_btn, 7, 0)
    self.grid.addWidget(com_lbl, 1, 0)
    self.grid.addWidget(speed_lbl, 2, 0)
    self.grid.addWidget(data_bits_lbl, 3, 0)
    self.grid.addWidget(stop_bit_lbl, 4, 0)
    self.grid.addWidget(parity_lbl, 5, 0)
    self.grid.addWidget(flow_control_lbl, 6, 0)
    com_lbl.resize(500, com_lbl.height())
    self.connPrefFiels = [com_list, speed_list, data_bits_list, stop_bit_list, parity_list, flow_control_list]

This are all commands that i can find. What to do with them? How to test them ?

Sorry for the delay. Was on a trip visiting family and busy with my own actual research work.

Unfortunately, there really aren’t a lot of good options here. Free and open source packages created y volunteers can and do go unmaintained, and while the beauty of open source is that you can modify them yourself to work again, if you can’t get this or any of the other packages working, you have a few optoins, none of them ideal:

  • Create your own (e.g. you can start with a simple CLI/Python API client that implemented the basic functionality instead of a fancy GUI, which should be much simpler and should be able to leverage more existing packages to do most of the heavy lifting)
  • Use a commercial package that does what you want (if any exist), or
  • Pay a more experienced developer to either maintain the existing packages, or write a new one for you
  • Do something else

Unfortunately, while I wish I could help more, I have a huge backlog of work on active, widely used FOSS projects, Python standardization and documentation efforts, helping many other users, as well as my own “day job” research for NASA that I’ve been neglecting in favor of Python and FOSS volunteer projects. I’d suggest finding someone at your institution, workplace, organization or other group, preferably at elast familiar with Xbee.

for i in XbeeCommands.ALL_CLASSES:

Looks like the commands are listed in XbeeCommands.ALL_CLASSES, wherever that is (a third party package, I’m guessing). You can look there for details.

What I would suggest is instead of trying to get this whole complex, unmaintained, GUI application working, is just starting with writing a simple Python script to send the Xbee command(s) you want and processing the responses, using whatever existing helper library that this GUI already uses. This gives you the simplest minimum viable product you can have, and you can then expand the functionality with a CLI, and if you really need it, a simple GUI using e.g. Tkinter built in to Python.

Instead of trying to figure out the intricacies of an existing application as a beginner, its usually better to start small, get something working and then iterate from there step by step toward something more complex.This both maximizes productivity and helps you learn step by step, while avoiding much of the frustration and feeling of being overwhelmed that it seems like the current approach is resulting in—sort of like starting shallow in a pool and gradually progressing to deeper water and more challenging dives, rather than trying to jump right to scuba diving inside a boat wreck 100 m underwater.