diff --git a/client/py/audio_manager.py b/client/py/audio_manager.py new file mode 100644 index 0000000..de90baf --- /dev/null +++ b/client/py/audio_manager.py @@ -0,0 +1,64 @@ +import vlc +import time + +#instance = vlc.Instance("-vvv", "--no-video", "--repeat") +#player = instance.media_player_new() +#media = instance.media_new("rtsp://piscan-arbor:8554/audio", "network-caching=25") +#player.set_media(media) +#player.play() +#while(True): +# time.sleep(1) + +class AudioManager: + vlc = None + player = None + media = None + + def __init__(self): + self.vlc = vlc.Instance('--no-video', '--repeat', '-v') + + def close(self): + + if self.player: + self.player.stop() + self.player.release() + + self.vlc.release() + + def startRtspAudioStream(self, host, port): + print('create media') + self.media = self.vlc.media_new('rtsp://' + host + ':' + port + '/audio', 'network-caching=50') + print('create new player') + self.player = self.media.player_new_from_media() + print('start player') + self.player.play() + + def stopRtspAudioStream(self): + print('stopping audio') + if self.player: + self.player.stop() + self.player.release() + self.player = None + self.media.release() + self.media = None + + def setAudioVolume(self, level : int): + self.player.audio_set_volume(level) + + def setAudioMute(self, mute : bool): + self.player.audio_set_mute(mute) + +if __name__ == '__main__': + audio = AudioManager() + + audio.startRtspAudioStream("pibox-airglow", "8554") + + try: + while(True): + time.sleep(1) + except KeyboardInterrupt: + pass + + audio.stopRtspAudioStream() + + audio.close() diff --git a/client/py/client.py b/client/py/client.py index 0c43874..8822dd4 100644 --- a/client/py/client.py +++ b/client/py/client.py @@ -12,6 +12,7 @@ from threading import Thread from time import sleep +import audio_manager import common import constants import connect @@ -27,6 +28,7 @@ import google.protobuf.message as proto class PiScanClient(QWidget, common.AppInterface): dataReceived = Signal(bytes) + manualDisconnectInitiated = False def __init__(self, parent=None, address=None, port=None): super(PiScanClient, self).__init__(parent) @@ -59,6 +61,8 @@ class PiScanClient(QWidget, common.AppInterface): self.inmsg = messages_pb2.ServerToClient() + self.audio = audio_manager.AudioManager() + def mainWidget(self): return self.window @@ -72,16 +76,28 @@ class PiScanClient(QWidget, common.AppInterface): self.setWindowMode(common.WindowMode.DIALOG) def receive(self): + disconnectMessage = '' + while True: try: data = self.sock.recv(2048) + if not data: + disconnectMessage = 'Connection closed by host' + break self.dataReceived.emit(data) + except ConnectionAbortedError: + if not self.manualDisconnectInitiated: + disconnectMessage = 'Connection aborted' + break except: e = sys.exc_info()[0] - self.showConnectDialog(repr(e)) + disconnectMessage = 'Unhandled exception: ' + str(e) break - print("Closing connection") + print("Closing connection. Reason: " + disconnectMessage) + self.audio.stopRtspAudioStream() + self.manualDisconnectInitiated = False + self.showConnectDialog(disconnectMessage) def handleReceived(self, data): self.inmsg.ParseFromString(data) @@ -110,18 +126,13 @@ class PiScanClient(QWidget, common.AppInterface): def closeEvent(self, event): print('Exiting...') - try: - if self.sock: - self.sock.close() - #self.rcv_thread.join() - except: - pass + self.disconnect() ## AppInterface methods #def attemptConnection(self, sock): - def completeConnection(self, sock): + def completeConnection(self, sock, host, use_audio, audio_port): self.sock = sock self.rcv_thread = Thread(target=self.receive) self.rcv_thread.start() @@ -141,7 +152,12 @@ class PiScanClient(QWidget, common.AppInterface): message3 = messages_pb2.ClientToServer() message3.type = messages_pb2.ClientToServer.Type.Value('GENERAL_REQUEST') message3.generalRequest.type = request_pb2.GeneralRequest.RequestType.Value('SYSTEM_INFO') - self.sock.send(message3.SerializeToString()) + self.sock.send(message3.SerializeToString()) + + if use_audio: + self.audio.startRtspAudioStream(host, audio_port) + self.setAudioVolume(50) + self.scanner.setVolumeVisible(use_audio) def scan(self): message = messages_pb2.ClientToServer() @@ -216,6 +232,21 @@ class PiScanClient(QWidget, common.AppInterface): else: self.setWindowTitle('PiScan') + def disconnect(self): + self.manualDisconnectInitiated = True + try: + if self.sock: + self.sock.close() + #self.rcv_thread.join() + except: + pass + + def setAudioVolume(self, level): + self.audio.setAudioVolume(level) + + def setAudioMute(self, mute): + self.audio.setAudioMute(mute) + class HostWindow(QtWidgets.QMainWindow): def __init__(self, parent=None, address=None, port=None): super(HostWindow, self).__init__(parent) diff --git a/client/py/common.py b/client/py/common.py index 95f58ce..389ce71 100644 --- a/client/py/common.py +++ b/client/py/common.py @@ -31,7 +31,7 @@ class AppInterface: #def attemptConnection(self, sock): # pass - def completeConnection(self, sock): + def completeConnection(self, sock, host, use_audio, audio_port): pass def scan(self): @@ -75,3 +75,12 @@ class AppInterface: def clearWindowTitleInfo(self): pass + + def disconnect(self): + pass + + def setAudioVolume(self, level): + pass + + def setAudioMute(self, mute): + pass diff --git a/client/py/connect.py b/client/py/connect.py index 633635e..38086ec 100644 --- a/client/py/connect.py +++ b/client/py/connect.py @@ -1,7 +1,7 @@ import sys from PySide2.QtUiTools import QUiLoader -from PySide2.QtWidgets import QWidget, QLabel, QPushButton, QLineEdit +from PySide2.QtWidgets import QWidget, QLabel, QPushButton, QLineEdit, QCheckBox from PySide2.QtCore import QObject from PySide2.QtGui import QMovie, QPixmap @@ -21,6 +21,9 @@ class ConnectDialog: self.logo = parentWindow.findChild(QLabel, 'connect_logoImage') self.hostLabel = parentWindow.findChild(QLabel, 'hostLabel') self.portLabel = parentWindow.findChild(QLabel, 'hostPortLabel') + self.audioCheckBox = parentWindow.findChild(QCheckBox, 'connect_audioCheckBox') + self.rtspPortPanel = parentWindow.findChild(QWidget, 'connect_rtspPortPanel') + self.rtspPortLineEdit = parentWindow.findChild(QLineEdit, 'connect_rtspPortLineEdit') self.logo.setPixmap(QPixmap("resources/icon-256.png")) self.logo.setVisible(False) @@ -39,6 +42,8 @@ class ConnectDialog: self.hostLineEdit.returnPressed.connect(self.onConfirm) self.portLineEdit.returnPressed.connect(self.onConfirm) + self.rtspPortPanel.setVisible(False) + def onConfirm(self): host = self.hostLineEdit.text() port = int(self.portLineEdit.text()) @@ -62,11 +67,19 @@ class ConnectDialog: self.connectIndicator.setVisible(False) - common.getApp().completeConnection(sock) + use_audio = self.audioCheckBox.isChecked() + audio_port = self.rtspPortLineEdit.text() + common.getApp().completeConnection(sock, address, use_audio, audio_port) + except ConnectionRefusedError: + self.connectFailed('Connect failed - Connection refused') + except gaierror as err: + self.connectFailed('Connect failed - ' + str(err)) + except TimeoutError: + self.connectFailed('Connect failed - Timed out') except: e = sys.exc_info()[0] - self.connectFailed(repr(e)) + self.connectFailed('Connect failed - Unhandled exception: ' + str(e)) def contextWait(self): diff --git a/client/py/scan_client.ui b/client/py/scan_client.ui index 23a2f3f..95c21c2 100644 --- a/client/py/scan_client.ui +++ b/client/py/scan_client.ui @@ -481,10 +481,47 @@ - 1 + 0 + + + + + + + Qt::RichText + + + :/indicator/loader-small.gif + + + Qt::AlignHCenter|Qt::AlignTop + + + + + + + + 0 + 0 + + + + + 12 + + + + Connect + + + Connect + + + @@ -967,6 +1004,9 @@ 12 + + Host address + localhost @@ -1439,6 +1479,9 @@ 12 + + TCP Port + 1234 @@ -1447,24 +1490,541 @@ - - + + - + 0 0 - - - 12 - - - - Connect - + + + 0 + + + 9 + + + + + + 0 + 0 + + + + + 12 + + + + Connect with audio + + + Audio + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 12 + + + + Audio Port + + + + + + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 127 + 127 + 127 + + + + + + + 170 + 170 + 170 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 127 + 127 + 127 + + + + + + + 170 + 170 + 170 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + + + 127 + 127 + 127 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 127 + 127 + 127 + + + + + + + 170 + 170 + 170 + + + + + + + 127 + 127 + 127 + + + + + + + 255 + 255 + 255 + + + + + + + 127 + 127 + 127 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + + + 12 + + + + RTSP port + + + 8554 + + + + + + + + + + @@ -1523,61 +2083,6 @@ - - - - - 0 - 0 - - - - - 0 - - - 9 - - - - - - 0 - 0 - - - - - 12 - - - - Audio - - - - - - - - - - - - - Qt::RichText - - - :/indicator/loader-small.gif - - - Qt::AlignHCenter|Qt::AlignTop - - - - - - @@ -1623,8 +2128,11 @@ + + Disconnect + - + -X- @@ -1642,7 +2150,10 @@ - + + + Settings + ... @@ -1662,7 +2173,10 @@ - + + + Connection info + ... @@ -1726,6 +2240,15 @@ 0 + + Volume + + + 100 + + + 50 + Qt::Vertical @@ -1733,8 +2256,11 @@ + + Mute + - + M true @@ -1810,6 +2336,9 @@ 16 + + Scan mode + S @@ -1827,6 +2356,9 @@ + + Edit entry + @@ -1845,6 +2377,9 @@ 16 + + Entry index + 0-0 @@ -1884,6 +2419,9 @@ 5 + + Signal level + 24 @@ -1908,6 +2446,9 @@ 20 + + System tag + System Tag @@ -1923,6 +2464,9 @@ 20 + + Entry tag + Entry Tag @@ -1955,45 +2499,6 @@ 0 - - - - - 0 - 0 - - - - - 16 - - - - 000.0000 MHz - - - - - - - - 0 - 0 - - - - - 16 - - - - Modulation - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - @@ -2015,23 +2520,10 @@ 0 - - - - - 0 - 0 - - - - - - - - + 0 0 @@ -2042,14 +2534,50 @@ - LO: N + LO: + + + + + + + + 12 + + + + Cycle lockout options + + + None - + + + + + 0 + 0 + + + + + 16 + + + + Current frequency + + + 000.0000 MHz + + + + @@ -2062,6 +2590,9 @@ 16 + + Scan delay + D:0.0s @@ -2070,6 +2601,30 @@ + + + + + 0 + 0 + + + + + 16 + + + + Modulation + + + Modulation + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + @@ -2147,8 +2702,17 @@ + + + 75 + true + + + + Open/close sidebar + - + < true @@ -2226,6 +2790,9 @@ 0 + + Set squelch level + -100 @@ -2268,6 +2835,9 @@ 0 + + Set tuner gain + -1 @@ -2325,6 +2895,9 @@ true + + Scan + Scan @@ -2339,6 +2912,9 @@ true + + Hold entry + Hold @@ -2353,6 +2929,9 @@ true + + Manual entry + Manual @@ -2367,6 +2946,9 @@ true + + Browse entries + Browser @@ -2850,6 +3432,9 @@ 0 + + Back + Back @@ -2876,7 +3461,7 @@ - 2 + 0 @@ -3380,6 +3965,9 @@ 12 + + Frequency in megahertz + 000.0000 MHz @@ -3849,6 +4437,9 @@ 12 + + Modulation + @@ -4300,6 +4891,9 @@ 12 + + Tune + Tune @@ -4323,7 +4917,7 @@ 0 0 - 443 + 179 223 @@ -7813,5 +8407,38 @@ - + + + scanner_sidebarToggle + toggled(bool) + scanner_sidebarPanel + setVisible(bool) + + + 390 + 126 + + + 439 + 125 + + + + + connect_audioCheckBox + toggled(bool) + connect_rtspPortPanel + setVisible(bool) + + + 147 + 137 + + + 239 + 167 + + + + diff --git a/client/py/scanner.py b/client/py/scanner.py index f2ad3d4..d3eb963 100644 --- a/client/py/scanner.py +++ b/client/py/scanner.py @@ -26,7 +26,8 @@ class Scanner: self.modulationLabel = parentWindow.findChild(QLabel, 'scanner_modulationLabel') self.systemTagLabel = parentWindow.findChild(QLabel, 'scanner_systemTagLabel') self.entryNumLabel = parentWindow.findChild(QLabel, 'scanner_entryNumLabel') - self.lockoutCheckbox = parentWindow.findChild(QCheckBox, 'scanner_lockoutCheckbox') + self.lockoutDurationLabel = parentWindow.findChild(QLabel, 'scanner_lockoutDurationLabel') + self.lockoutDurationButton = parentWindow.findChild(QPushButton, 'scanner_lockoutDurationButton') self.scanIndicator = parentWindow.findChild(QLabel, 'scanner_scanIndicator') self.gainSlider = parentWindow.findChild(QSlider, 'scanner_gainSlider') ##self.gainLabel = parentWindow.findChild(QLabel, 'scanner_gainLabel') @@ -42,6 +43,16 @@ class Scanner: self.sidebarToggleButton = parentWindow.findChild(QToolButton, 'scanner_sidebarToggle') self.sidebarPanel = parentWindow.findChild(QWidget, 'scanner_sidebarPanel') + self.disconnectButton = parentWindow.findChild(QToolButton, 'scanner_disconnectButton') + self.settingsButton = parentWindow.findChild(QToolButton, 'scanner_settingsButton') + self.connectInfoButton = parentWindow.findChild(QToolButton, 'scanner_connectInfoButton') + + self.volumeControlPanel = parentWindow.findChild(QWidget, 'scanner_volumeControl') + self.volumeSlider = parentWindow.findChild(QWidget, 'scanner_volumeSlider') + self.muteButton = parentWindow.findChild(QWidget, 'scanner_volumeMute') + + self.entryEditButton = parentWindow.findChild(QToolButton, 'scanner_entryEditButton') + self.fnButton1.clicked.connect(self.onFnButton1) self.fnButton2.clicked.connect(self.onFnButton2) self.fnButton3.clicked.connect(self.onFnButton3) @@ -50,10 +61,22 @@ class Scanner: self.squelchSlider.valueChanged.connect(self.onsquelchSlider) self.sidebarToggleButton.clicked.connect(self.onSidebarToggle) - self.sidebarOpen = True + self.sidebarOpen = False + self.sidebarPanel.setVisible(False) + + self.disconnectButton.clicked.connect(self.onDisconnectButton) + + self.volumeSlider.valueChanged.connect(self.onVolumeSlider) + self.muteButton.toggled.connect(self.onMuteButton) #temporary since settins dialog is not yet implemented - self.fnButton4.setEnabled(False) + self.volumeControlPanel.setVisible(False) + self.lockoutDurationButton.setVisible(False) + self.lockoutDurationLabel.setVisible(False) + self.fnButton4.setVisible(False) + self.settingsButton.setVisible(False) + self.connectInfoButton.setVisible(False) + self.entryEditButton.setVisible(False) movie = QMovie("resources/bar-scan.gif") movie.start() @@ -178,9 +201,11 @@ class Scanner: def onSidebarToggle(self): if self.sidebarOpen: + self.sidebarToggleButton.setText('<') self.sidebarPanel.setVisible(False) self.sidebarOpen = False else: + self.sidebarToggleButton.setText('>') self.sidebarPanel.setVisible(True) self.sidebarOpen = True @@ -190,4 +215,17 @@ class Scanner: def setGainRange(self, minimum, maximum): self.gainSlider.setMinimum(minimum) - self.gainSlider.setMaximum(maximum) \ No newline at end of file + self.gainSlider.setMaximum(maximum) + + def onDisconnectButton(self): + common.getApp().disconnect() + + def onVolumeSlider(self, value): + common.getApp().setAudioVolume(value) + + def onMuteButton(self, value): + self.volumeSlider.setEnabled(not value) + common.getApp().setAudioMute(value) + + def setVolumeVisible(self, visible): + self.volumeControlPanel.setVisible(visible)