I have two Python scripts that i need to communicate with each another. The first is a GUI made in PySide2. The GUI consists of simple controls for controlling a bluetooth audio device (play, pause, next, previous, etc...). These commands operate with a second python script that i found. The second script is a loop that waits for these commands to be entered and responds once those commands are executed. I'm pretty new to programming, i'm guessing this is essentially connecting a front end with a back end, but its something i've never done before.
I've written a simplified version of my GUI to only display the controls i need. The "back-end" is also below but can originally be found here: https://scribles.net/controlling-bluetooth-audio-on-raspberry-pi/
I've previously asked a similar question and was given a solid and working answer by @eyllanesc here: Execute command to a Python script from separate Python script? However, using the QProcess method i could not work out how to get the print
outputs from the back-end into the front-end script. The error messages print correctly however. I have tried playing around with sys.stdout
in the back-end, variants of process.read
, and QByteArrays but can't seem to get anything going.
The other issue i'm having is that the script will only work if a bluetooth device is connected prior to starting the script. If i disconnect while it is running and try to reconnect, it will no longer accept commands. If there is also a way to monitor whether a device is playing/paused so that the play/pause button can update depending on the devices state, that would also be useful, but its not important at this stage.
Theres a number of ways that it can be done, but i feel that ultimately it would be better for me to have both scripts integrated into one, however i'm open to any solution that works. If anyone has any advice or can get me started i'd be very appreciative!
Front-end:
import sys
from PySide2.QtWidgets import *
class MainWindow(QWidget):
def __init__(self):
QWidget.__init__(self)
self.playbtn = QPushButton("Play")
self.nextbtn = QPushButton("Next")
self.prevbtn = QPushButton("Prev")
layout = QVBoxLayout()
layout.addWidget(self.playbtn)
layout.addWidget(self.nextbtn)
layout.addWidget(self.prevbtn)
self.setLayout(layout)
self.playbtn.released.connect(self.btnplay)
self.nextbtn.released.connect(self.btnnext)
self.prevbtn.released.connect(self.btnprev)
def btnplay(self): #play button turns into pause button upon being pressed
status = self.playbtn.text()
if status == "Play":
self.playbtn.setText("Pause")
print("Play Pressed")
elif status == "Pause":
self.playbtn.setText("Play")
print("Pause pressed")
def btnnext(self):
print("Next pressed")
def btnprev(self):
print("Prev pressed")
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
Back-end:
import dbus, dbus.mainloop.glib, sys
from gi.repository import GLib
def on_property_changed(interface, changed, invalidated):
if interface != 'org.bluez.MediaPlayer1':
return
for prop, value in changed.items():
if prop == 'Status':
print('Playback Status: {}'.format(value))
elif prop == 'Track':
print('Music Info:')
for key in ('Title', 'Artist', 'Album'):
print(' {}: {}'.format(key, value.get(key, '')))
def on_playback_control(fd, condition):
str = fd.readline()
if str.startswith('play'):
player_iface.Play()
elif str.startswith('pause'):
player_iface.Pause()
elif str.startswith('next'):
player_iface.Next()
elif str.startswith('prev'):
player_iface.Previous()
elif str.startswith('vol'):
vol = int(str.split()[1])
if vol not in range(0, 128):
print('Possible Values: 0-127')
return True
transport_prop_iface.Set(
'org.bluez.MediaTransport1',
'Volume',
dbus.UInt16(vol))
return True
if __name__ == '__main__':
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()
obj = bus.get_object('org.bluez', "/")
mgr = dbus.Interface(obj, 'org.freedesktop.DBus.ObjectManager')
player_iface = None
transport_prop_iface = None
for path, ifaces in mgr.GetManagedObjects().items():
if 'org.bluez.MediaPlayer1' in ifaces:
player_iface = dbus.Interface(
bus.get_object('org.bluez', path),
'org.bluez.MediaPlayer1')
elif 'org.bluez.MediaTransport1' in ifaces:
transport_prop_iface = dbus.Interface(
bus.get_object('org.bluez', path),
'org.freedesktop.DBus.Properties')
if not player_iface:
sys.exit('Error: Media Player not found.')
if not transport_prop_iface:
sys.exit('Error: DBus.Properties iface not found.')
bus.add_signal_receiver(
on_property_changed,
bus_name='org.bluez',
signal_name='PropertiesChanged',
dbus_interface='org.freedesktop.DBus.Properties')
GLib.io_add_watch(sys.stdin, GLib.IO_IN, on_playback_control)
GLib.MainLoop().run()
UPDATE 31/10/2020:
I've been playing around with the QProcess
class suggested in my earlier question linked above. By using it on the button press functions and adding sys.exit
after the command has been executed, it eliminates the need for a device to always be connected, but i still can't find a way to recieve the print
outputs from back-end script. It also feels like a really dirty way of working. It also retains the issue with the play/pause state not automatically updating. If anyone has any suggestions i would be very grateful!
import sys
import os.path
from PySide2.QtCore import *
from PySide2.QtWidgets import *
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
class MainWindow(QWidget):
def __init__(self):
QWidget.__init__(self)
self.playbtn = QPushButton("Play")
self.nextbtn = QPushButton("Next")
self.prevbtn = QPushButton("Prev")
layout = QVBoxLayout()
layout.addWidget(self.playbtn)
layout.addWidget(self.nextbtn)
layout.addWidget(self.prevbtn)
self.setLayout(layout)
self.playbtn.released.connect(self.btnplay)
self.nextbtn.released.connect(self.btnnext)
self.prevbtn.released.connect(self.btnprev)
def btnplay(self):
self.process = QProcess()
self.process.readyReadStandardError.connect(self.handle_readyReadStandardError)
self.process.readyReadStandardOutput.connect(self.handle_readyReadStandardOutput)
self.process.setProgram(sys.executable)
script_path = os.path.join(CURRENT_DIR, "test2.py")
self.process.setArguments([script_path])
self.process.start()
status = self.playbtn.text()
if status == "Play":
command = "play"
self.playbtn.setText("Pause")
print("play pressed")
elif status == "Pause":
command = "pause"
self.playbtn.setText("Play")
print("pause pressed")
msg = "{}\n".format(command)
self.process.write(msg.encode())
def btnnext(self):
self.process = QProcess()
self.process.readyReadStandardError.connect(self.handle_readyReadStandardError)
self.process.readyReadStandardOutput.connect(self.handle_readyReadStandardOutput)
self.process.setProgram(sys.executable)
script_path = os.path.join(CURRENT_DIR, "test2.py")
self.process.setArguments([script_path])
self.process.start()
command = "next"
msg = "{}\n".format(command)
self.process.write(msg.encode())
print("next pressed")
def btnprev(self):
self.process = QProcess()
self.process.readyReadStandardError.connect(self.handle_readyReadStandardError)
self.process.readyReadStandardOutput.connect(self.handle_readyReadStandardOutput)
self.process.setProgram(sys.executable)
script_path = os.path.join(CURRENT_DIR, "test2.py")
self.process.setArguments([script_path])
self.process.start()
command = "prev"
msg = "{}\n".format(command)
self.process.write(msg.encode())
print("prev pressed")
def handle_readyReadStandardError(self):
print(self.process.readAllStandardError().data().decode())
def handle_readyReadStandardOutput(self):
print(self.process.readAllStandardOutput().data().decode())
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
IMHO the OP has an XY problem that adds unnecessary complexity to the application since the dbus eventloop can coexist with the Qt one as I show in the following example:
import sys
import dbus
import dbus.mainloop.glib
from PyQt5 import QtCore, QtWidgets
class AudioManager(QtCore.QObject):
statusChanged = QtCore.pyqtSignal(str)
infoChanged = QtCore.pyqtSignal(dict)
def __init__(self, parent=None):
super().__init__(parent)
self._player_iface = None
self._transport_prop_iface = None
def initialize(self):
bus = dbus.SystemBus()
obj = bus.get_object("org.bluez", "/")
mgr = dbus.Interface(obj, "org.freedesktop.DBus.ObjectManager")
player_iface = None
transport_prop_iface = None
for path, ifaces in mgr.GetManagedObjects().items():
if "org.bluez.MediaPlayer1" in ifaces:
player_iface = dbus.Interface(
bus.get_object("org.bluez", path), "org.bluez.MediaPlayer1"
)
elif "org.bluez.MediaTransport1" in ifaces:
transport_prop_iface = dbus.Interface(
bus.get_object("org.bluez", path), "org.freedesktop.DBus.Properties"
)
if not player_iface:
raise Exception("Error: Media Player not found.")
if not transport_prop_iface:
raise Exception("Error: DBus.Properties iface not found.")
self._player_iface = player_iface
self._transport_prop_iface = transport_prop_iface
bus.add_signal_receiver(
self.handle_property_changed,
bus_name="org.bluez",
signal_name="PropertiesChanged",
dbus_interface="org.freedesktop.DBus.Properties",
)
def play(self):
self._player_iface.Play()
def pause(self):
self._player_iface.Pause()
def next(self):
self._player_iface.Next()
def previous(self):
self._player_iface.Previous()
def set_volume(self, Volume):
if Volume not in range(0, 128):
raise ValueError("Possible Values: 0-127")
self._transport_prop_iface.Set(
"org.bluez.MediaTransport1", "Volume", dbus.UInt16(vol)
)
def handle_property_changed(self, interface, changed, invalidated):
if interface != "org.bluez.MediaPlayer1":
return
for prop, value in changed.items():
if prop == "Status":
self.statusChanged.emit(value)
elif prop == "Track":
info = dict()
for key in ("Title", "Artist", "Album"):
info[key] = str(value.get(key, ""))
self.infoChanged.emit(info)
class MainWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self._manager = AudioManager()
self._manager.infoChanged.connect(self.handle_info_changed)
self._manager.initialize()
self.playbtn = QtWidgets.QPushButton("Play")
self.nextbtn = QtWidgets.QPushButton("Next")
self.prevbtn = QtWidgets.QPushButton("Prev")
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.playbtn)
layout.addWidget(self.nextbtn)
layout.addWidget(self.prevbtn)
self.playbtn.released.connect(self._manager.play)
self.nextbtn.released.connect(self._manager.next)
self.prevbtn.released.connect(self._manager.previous)
def handle_info_changed(self, info):
print(info)
if __name__ == "__main__":
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()