#!/usr/bin/python3 import sys, getopt from PySide2 import QtWidgets from PySide2.QtUiTools import QUiLoader from PySide2.QtWidgets import * from PySide2.QtCore import * from socket import * from threading import Thread from time import sleep import ui_scan_client import audio_manager import common import constants import connect import dialogs import scanner sys.path.insert(1, '../../proto') import messages_pb2 import context_pb2 import request_pb2 import google.protobuf.message as proto class PiScanClient(QWidget, common.AppInterface): dataReceived = Signal(bytes) manualDisconnectInitiated = False manualDisconnectMessage = None def __init__(self, parent=None, address=None, port=None, use_audio=False, rtsp_port=None): super(PiScanClient, self).__init__(parent) common.setInstance(self) self.window = ui_scan_client.Ui_Form() self.window.setupUi(self) self.parentWindow = parent #layout = QGridLayout(self) #layout.setContentsMargins(0, 0, 0, 0) #layout.addWidget(self.window) self.dataReceived.connect(self.handleReceived) self.connectDialog = connect.ConnectDialog(self.window, address, port) self.scanner = scanner.Scanner(self.window) self.dialogs = dialogs.Dialogs(self.window) self.contextStack = self.window.contextStack #self.setWindowMode(common.WindowMode.CONNECT) self.showConnectDialog() #self.setWindowMode(common.WindowMode.SCANNER) #self.window.show() self.inmsg = messages_pb2.ServerToClient() self.use_audio = use_audio self.audio = audio_manager.AudioManager() def mainWidget(self): return self.window def setWindowMode(self, mode): self.mode = mode self.contextStack.setCurrentIndex(mode.value) def showDialog(self, dialog): self.dialogs.setDialog(dialog) self.setWindowMode(common.WindowMode.DIALOG) def receive(self): disconnectMessage = None 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 OSError as err: if not self.manualDisconnectInitiated: disconnectMessage = str(err) break except: e = sys.exc_info()[0] disconnectMessage = 'Unhandled exception: ' + str(e) break if not disconnectMessage: disconnectMessage = self.manualDisconnectMessage if disconnectMessage: print('Closing connection. Reason: ' + disconnectMessage) else: print('Disconnecting...') if self.use_audio: self.audio.stopRtspAudioStream() self.manualDisconnectInitiated = False self.showConnectDialog(disconnectMessage) def handleReceived(self, data): self.inmsg.ParseFromString(data) self.decodeMessage(message=self.inmsg) def decodeMessage(self, message): #print(message) if message.WhichOneof('content') == 'systemInfo': #print('system info') self.scanner.setSquelchRange(message.systemInfo.squelchScaleMin, message.systemInfo.squelchScaleMax) for mode in message.systemInfo.supportedModulations: self.dialogs.manualEntry.addModulation(mode) elif message.type == messages_pb2.ServerToClient.Type.Value('SCANNER_CONTEXT'): #print('scanner context') self.scanner.updateScanContext(message.scannerContext) if self.contextWait: self.setWindowMode(common.WindowMode.SCANNER) self.contextWait = False elif message.type == messages_pb2.ServerToClient.Type.Value('DEMOD_CONTEXT'): #print('demod context') self.scanner.updateDemodContext(message.demodContext) #elif message.type == messages_pb2.ServerToClient.Type.Value('GENERAL_MESSAGE'): elif message.WhichOneof('content') == 'signalLevel': self.scanner.updateSignalIndicator(message.signalLevel.level) def closeEvent(self, event): print('Exiting...') self.disconnect() ## AppInterface methods #def attemptConnection(self, sock): def completeConnection(self, sock, host, use_audio, audio_port): self.sock = sock self.rcv_thread = Thread(target=self.receive, name='socket monitor') self.rcv_thread.start() self.contextWait = True self.connectDialog.contextWait() sleep(1) ## TODO sync issue - the responses are received before the application is ready to process them message1 = messages_pb2.ClientToServer() message1.type = messages_pb2.ClientToServer.Type.Value('GENERAL_REQUEST') message1.generalRequest.type = request_pb2.GeneralRequest.RequestType.Value('SCANNER_CONTEXT') self.sendData(message1.SerializeToString()) sleep(0.25) message2 = messages_pb2.ClientToServer() message2.type = messages_pb2.ClientToServer.Type.Value('GENERAL_REQUEST') message2.generalRequest.type = request_pb2.GeneralRequest.RequestType.Value('DEMOD_CONTEXT') self.sendData(message2.SerializeToString()) sleep(0.25) message3 = messages_pb2.ClientToServer() message3.type = messages_pb2.ClientToServer.Type.Value('GENERAL_REQUEST') message3.generalRequest.type = request_pb2.GeneralRequest.RequestType.Value('SYSTEM_INFO') self.sendData(message3.SerializeToString()) if use_audio: self.use_audio = self.audio.startRtspAudioStream(host, audio_port) #self.setAudioVolume(50) else: self.use_audio = False self.scanner.setVolumeVisible(self.use_audio) def scan(self): message = messages_pb2.ClientToServer() message.type = messages_pb2.ClientToServer.Type.Value('SCANNER_STATE_REQUEST') message.scanStateRequest.state = request_pb2.ScannerStateRequest.NewState.Value('SCAN') self.sendData(message.SerializeToString()) def hold(self): message = messages_pb2.ClientToServer() message.type = messages_pb2.ClientToServer.Type.Value('SCANNER_STATE_REQUEST') message.scanStateRequest.state = request_pb2.ScannerStateRequest.NewState.Value('HOLD') self.sendData(message.SerializeToString()) def manualEntry(self, frequency, modulation): message = messages_pb2.ClientToServer() message.type = messages_pb2.ClientToServer.Type.Value('SCANNER_STATE_REQUEST') message.scanStateRequest.state = request_pb2.ScannerStateRequest.NewState.Value('MANUAL') message.scanStateRequest.manFreq = frequency message.scanStateRequest.manModulation = modulation self.sendData(message.SerializeToString()) def showConnectDialog(self, errorMessage = ''): self.clearWindowTitleInfo() self.connectDialog.connectFailed(errorMessage) self.setWindowMode(common.WindowMode.CONNECT) def showManualEntryDialog(self): self.lastMode = self.mode self.showDialog(common.DialogMode.MANUAL_ENTRY) def showEditEntryDialog(self): self.lastMode = self.mode self.showDialog(common.DialogMode.EDIT_ENTRY) def showEntryBrowser(self): self.lastMode = self.mode self.showDialog(common.DialogMode.SYSTEM_BROWSER) def showSettingsDialog(self): self.lastMode = self.mode self.showDialog(common.DialogMode.SYSTEM_SETTINGS) def setGain(self, value): message = messages_pb2.ClientToServer() message.type = messages_pb2.ClientToServer.Type.Value('DEMOD_REQUEST') message.demodRequest.type = request_pb2.DemodRequest.DemodFunc.Value('SET_GAIN') message.demodRequest.level = value self.sendData(message.SerializeToString()) def setSquelch(self, value): message = messages_pb2.ClientToServer() message.type = messages_pb2.ClientToServer.Type.Value('DEMOD_REQUEST') message.demodRequest.type = request_pb2.DemodRequest.DemodFunc.Value('SET_SQUELCH') message.demodRequest.level = value self.sendData(message.SerializeToString()) def dialogClosed(self): self.setWindowMode(self.lastMode) def tryConnect(self, address, port, use_audio, rtsp_port): self.connectDialog.tryConnect(address, port, use_audio, rtsp_port) def setWindowTitleInfo(self, message): if isinstance(self.parentWindow, QtWidgets.QMainWindow): self.parentWindow.setWindowTitle('PiScan | ' + message) else: self.setWindowTitle('PiScan | ' + message) def clearWindowTitleInfo(self): if isinstance(self.parentWindow, QtWidgets.QMainWindow): self.parentWindow.setWindowTitle('PiScan') else: self.setWindowTitle('PiScan') def disconnect(self, message=''): self.manualDisconnectInitiated = True self.manualDisconnectMessage = message try: if self.sock: self.sock.close() #self.rcv_thread.join() except Exception as e: print(e) def setAudioVolume(self, level): if self.use_audio: self.audio.setAudioVolume(level) def setAudioMute(self, mute): if self.use_audio: self.audio.setAudioMute(mute) def sendData(self, message): try: self.sock.send(message) except OSError as err: self.disconnect('Connection error: ' + str(err)) except Exception as e: print(e) class HostWindow(QtWidgets.QMainWindow): def __init__(self, parent=None, address=None, port=None, use_audio=False, rtsp_port=None): super(HostWindow, self).__init__(parent) form = PiScanClient(self, address, port, use_audio, rtsp_port) self.setPalette(form.palette()) self.setGeometry(form.geometry()) self.setWindowTitle(form.windowTitle()) self.setCentralWidget(form) #self.actionQuit.triggered.connect(self.closeEvent) #self.show() if address: if not port: port = constants.DEFAULT_TCP_PORT form.tryConnect(address, port, use_audio, rtsp_port) def closeEvent(self, event): #print('exit') common.getApp().closeEvent(event) event.accept() if __name__ == '__main__': shortOpts = 'la:p:wsr:' longOpts = ['--local', '--address', '--port', '--pi_mode', '--audio', '--rtsp_port'] options, remainder = getopt.getopt(sys.argv[1:], shortOpts, longOpts) address = None port = None piMode = False use_audio = False rtsp_port = 8554 for opt, arg in options: if opt in ('-l', '--local'): address = 'localhost' elif opt in ('-a', '--address'): address = arg elif opt in ('-p', '--port'): port = int(arg) elif opt in ('-w', '--pi_mode'): piMode = True elif opt in ('-s', '--audio'): use_audio = True elif opt in ('-r', '--rtsp_port'): rtsp_port = int(arg) app = QApplication(sys.argv) window = HostWindow(address=address, port=port, use_audio=use_audio, rtsp_port=rtsp_port) if piMode: flags = Qt.WindowFlags(Qt.CustomizeWindowHint | Qt.FramelessWindowHint | Qt.Tool) window.setWindowFlags(flags) window.showMaximized() else: window.show() sys.exit(app.exec_())