440 wiersze
20 KiB
Python
440 wiersze
20 KiB
Python
import sys
|
|
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QComboBox, QLabel, QLineEdit, QGroupBox, QFormLayout, QProgressBar, QTabWidget, QCheckBox, QFileDialog, QListWidget, QListWidgetItem
|
|
from PyQt5.QtSerialPort import QSerialPort, QSerialPortInfo
|
|
from PyQt5.QtCore import pyqtSlot, QStringListModel
|
|
from PyQt5.QtGui import QIcon
|
|
from objects import TPoint
|
|
|
|
class SerialConfigurator(QWidget):
|
|
def __init__(self, confprot, config_objects, serialPort, fw_uploader, img_uploader):
|
|
self.confprot = confprot
|
|
self.config = config_objects
|
|
self.serialPort = serialPort
|
|
self.fw_uploader = fw_uploader
|
|
self.img_uploader = img_uploader
|
|
self.fw_file = None
|
|
super().__init__()
|
|
self.initUI()
|
|
self.init_layout_lists()
|
|
|
|
def show(self):
|
|
super().show()
|
|
self.resize(500, self.size().height())
|
|
self.setFixedSize(self.size())
|
|
|
|
def init_layout_lists(self):
|
|
self.aprsSymbolMap = {
|
|
"BALLOON" : 6,
|
|
"CAR" : 3,
|
|
"ROCKET" : 5,
|
|
"WX_STATION" : 1,
|
|
"DIGI" : 0,
|
|
}
|
|
self.aprsSymbolMapInverse = {v: k for k, v in self.aprsSymbolMap.items()}
|
|
|
|
self.geoConfigModeMap = {
|
|
'TX INSIDE' : 0,
|
|
'TX OUTSIDE' : 1
|
|
}
|
|
self.geoConfigModeMapInverse = {v: k for k, v in self.geoConfigModeMap.items()}
|
|
|
|
# common setters
|
|
self.str_to_qlabel = lambda x, y: y.setText(str(x))
|
|
self.str_to_qcombobox = lambda x, y: y.setCurrentText(str(x))
|
|
cord_lines_to_objs = lambda x : [TPoint(int(float(x[i].text()) * 100), int(float(x[i+1].text()) * 100)) for i in range(0, len(x), 2)]
|
|
def objs_to_cord_lines(x, y):
|
|
for i in range(0, len(y), 2):
|
|
y[i].setText(str(float(x[int(i/2)].x)/100))
|
|
y[i+1].setText(str(float(x[int(i/2)].y)/100))
|
|
# stefan obj(idx, attribute) | qtObj | setter | getter
|
|
|
|
self.config_map = [
|
|
[('aprs_config', "callsign"), self.aprsCallsign, lambda x: x.text(), self.str_to_qlabel ],
|
|
[('aprs_config', "destination"), None, None, None ],
|
|
[('aprs_config', "digi_path"), self.aprsDigiPath, lambda x: x.text(), self.str_to_qlabel ],
|
|
[('aprs_config', "callsign_designator"), self.aprsDesignator, lambda x: int(x.currentText()), self.str_to_qcombobox ],
|
|
[('aprs_config', "destination_designator"), None, None, None ],
|
|
[('aprs_config', "symbol"), self.aprsSymbol, lambda x: int(self.aprsSymbolMap[x.currentText()]), lambda x,y : self.str_to_qcombobox(self.aprsSymbolMapInverse[x], y) ],
|
|
|
|
[('freq_table_config', "frequency"), self.frequencyLines, lambda x: int(float(x.text())*1000), lambda x,y : self.str_to_qlabel(float(x)/1000, y) ],
|
|
[('freq_table_config', "telemetry_psc"), self.telemetryPrescalers, lambda x: int(x.currentText()), self.str_to_qcombobox ],
|
|
[('freq_table_config', "position_psc"), self.positionPrescalers, lambda x: int(x.currentText()), self.str_to_qcombobox ],
|
|
[('freq_table_config', "radio_mode"), self.modeBoxes, lambda x: int(self.modes.index(x.currentText())), lambda x, y : y.setCurrentText(self.modes[x]) ],
|
|
|
|
[('geofencing_config', "points"), self.geoConfigCordLines, cord_lines_to_objs, objs_to_cord_lines ],
|
|
[('geofencing_config', "b_inside"), self.geoConfigModeBoxes, lambda x: int(self.geoConfigModeMap[x.currentText()]), lambda x,y : self.str_to_qcombobox(self.geoConfigModeMapInverse[x], y) ],
|
|
|
|
[('sstv_config', "sstv_text_field"), self.sstvTextHeader, lambda x: x.text(), self.str_to_qlabel ],
|
|
[('sstv_config', "images_cnt"), self.sstvImageCnt, lambda x: int(x.currentText()), self.str_to_qcombobox ],
|
|
[('sstv_config', "images_per_freq"), self.sstvImageCntPerFreq, lambda x: int(x.currentText()), self.str_to_qcombobox ],
|
|
[('sstv_config', "b_header_enabled"), self.sstvBHeaderEnabled, lambda x: int(x.isChecked()), lambda x,y : y.setChecked( x > 0 ) ],
|
|
]
|
|
|
|
@pyqtSlot()
|
|
def connect(self):
|
|
portName = self.comboBox.currentText()
|
|
# self.serialPort = QSerialPort()
|
|
self.serialPort.setPortName(portName)
|
|
self.serialPort.setBaudRate(115200)
|
|
|
|
if self.serialPort.open(QSerialPort.ReadWrite, ):
|
|
self.connectionLabel.setText("Connected to " + portName)
|
|
else:
|
|
self.connectionLabel.setText("Failed to connect to " + portName)
|
|
|
|
def set_command(self):
|
|
for key, obj_qt, setter, getter in self.config_map:
|
|
if obj_qt == None:
|
|
continue
|
|
dict_idx, attr = key
|
|
obj = self.config[dict_idx]
|
|
if not hasattr(obj, 'table_size'):
|
|
setattr(obj, attr, setter(obj_qt))
|
|
else:
|
|
for i in range(min(obj.table_size, len(obj_qt))):
|
|
setattr(obj.objects[i], attr, setter(obj_qt[i]))
|
|
self.confprot.set_all_sync(self.update_progressbar)
|
|
|
|
def set_command_async(self):
|
|
print("set command requested")
|
|
for key, obj_qt, setter, getter in self.config_map:
|
|
if obj_qt == None:
|
|
continue
|
|
dict_idx, attr = key
|
|
obj = self.config[dict_idx]
|
|
if not hasattr(obj, 'table_size'):
|
|
setattr(obj, attr, setter(obj_qt))
|
|
else:
|
|
for i in range(min(obj.table_size, len(obj_qt))):
|
|
setattr(obj.objects[i], attr, setter(obj_qt[i]))
|
|
|
|
self.disable_buttons(True)
|
|
self.confprot.set_all_async(self.progress_callback)
|
|
|
|
def get_command_async(self):
|
|
self.disable_buttons(True)
|
|
self.confprot.get_all_async(self.progress_callback_get)
|
|
|
|
def progress_callback_get(self, progress):
|
|
self.progress_callback(progress)
|
|
if progress != 100:
|
|
return
|
|
#update qt objects
|
|
for key, obj_qt, setter, getter in self.config_map:
|
|
if obj_qt == None:
|
|
continue
|
|
dict_idx, attr = key
|
|
obj = self.config[dict_idx]
|
|
if not hasattr(obj, 'table_size'):
|
|
getter(getattr(obj, attr), obj_qt)
|
|
else:
|
|
for i in range(min(obj.table_size, len(obj_qt))):
|
|
getter(getattr(obj.objects[i], attr), obj_qt[i])
|
|
|
|
def progress_callback(self, progress):
|
|
if progress < 0 or progress == 100:
|
|
self.disable_buttons(False)
|
|
|
|
if progress >= 0:
|
|
self.update_progressbar(progress)
|
|
|
|
def disable_buttons(self, disabled):
|
|
self.getCommandButton.setDisabled(disabled)
|
|
self.setCommandButton.setDisabled(disabled)
|
|
self.fwUpdateUploadButton.setDisabled(disabled)
|
|
self.sstvImagesUploadButton.setDisabled(disabled)
|
|
|
|
def get_command(self):
|
|
self.confprot.get_all_sync(self.update_progressbar)
|
|
for key, obj_qt, setter, getter in self.config_map:
|
|
if obj_qt == None:
|
|
continue
|
|
dict_idx, attr = key
|
|
obj = self.config[dict_idx]
|
|
if not hasattr(obj, 'table_size'):
|
|
getter(getattr(obj, attr), obj_qt)
|
|
else:
|
|
for i in range(min(obj.table_size, len(obj_qt))):
|
|
getter(getattr(obj.objects[i], attr), obj_qt[i])
|
|
|
|
def update_progressbar(self, percent):
|
|
print(percent)
|
|
self.progressBar.setValue(int(percent))
|
|
|
|
def initUI(self):
|
|
self.setWindowTitle('SQ9P Tracker Configurator')
|
|
|
|
self.layout = QVBoxLayout()
|
|
self.setLayout(self.layout)
|
|
|
|
self.connectionSettings = QGroupBox('Connection Settings')
|
|
self.connectionSettingsLayout = QVBoxLayout()
|
|
self.connectionSettings.setLayout(self.connectionSettingsLayout)
|
|
|
|
self.uartConfigRowLayout = QHBoxLayout()
|
|
self.connectionSettingsLayout.addLayout(self.uartConfigRowLayout)
|
|
|
|
self.comboBox = QComboBox(self)
|
|
self.serialPorts = QSerialPortInfo.availablePorts()
|
|
for port in self.serialPorts:
|
|
self.comboBox.addItem(port.portName())
|
|
|
|
self.connectionLabel = QLabel(self)
|
|
self.connectionLabel.setText("Disconnected")
|
|
|
|
self.connectButton = QPushButton('Connect', self)
|
|
self.connectButton.clicked.connect(self.connect)
|
|
|
|
self.uartConfigRowLayout.addWidget(self.comboBox)
|
|
self.uartConfigRowLayout.addWidget(self.connectionLabel)
|
|
self.uartConfigRowLayout.addWidget(self.connectButton)
|
|
|
|
self.uartCommandRowLayout = QHBoxLayout()
|
|
self.connectionSettingsLayout.addLayout(self.uartCommandRowLayout)
|
|
|
|
self.getCommandButton = QPushButton('Get', self)
|
|
self.getCommandButton.clicked.connect(self.get_command_async)
|
|
self.setCommandButton = QPushButton('Set', self)
|
|
self.setCommandButton.clicked.connect(self.set_command_async)
|
|
|
|
self.uartCommandRowLayout.addWidget(self.getCommandButton)
|
|
self.uartCommandRowLayout.addWidget(self.setCommandButton)
|
|
|
|
self.progresBarLayout = QHBoxLayout()
|
|
self.connectionSettingsLayout.addLayout(self.progresBarLayout)
|
|
|
|
self.progressBar = QProgressBar(self)
|
|
self.progresBarLayout.addWidget(self.progressBar)
|
|
self.progressBar.setValue(0)
|
|
self.layout.addWidget(self.connectionSettings)
|
|
|
|
self.tabs = QTabWidget()
|
|
self.init_frequency_layout()
|
|
self.init_geoconig_layout()
|
|
self.init_sstv_layout()
|
|
self.init_update_layout()
|
|
|
|
self.multiConfig = QGroupBox('Multi Configuration')
|
|
self.multiConfigLayout = QVBoxLayout()
|
|
self.multiConfig.setLayout(self.multiConfigLayout)
|
|
|
|
self.multiConfigLayout.addWidget(self.tabs)
|
|
self.layout.addWidget(self.multiConfig)
|
|
#self.layout.addWidget(self.tabs)
|
|
# self.layout.addWidget(self.aprsSettings)
|
|
|
|
def init_frequency_layout(self):
|
|
self.frequencyConfigTab = QWidget()
|
|
self.frequencyConfigTabLayout = QHBoxLayout()
|
|
self.frequencyConfigTab.setLayout(self.frequencyConfigTabLayout)
|
|
|
|
self.frequencyConfigGroupBox = QGroupBox('Frequency Settings')
|
|
self.frequencyConfigLayout = QVBoxLayout()
|
|
self.frequencyConfigGroupBox.setLayout(self.frequencyConfigLayout)
|
|
# self.frequencyConfigTab.setLayout(self.frequencyConfigLayout)
|
|
self.frequencyConfigTabLayout.addWidget(self.frequencyConfigGroupBox)
|
|
self.tabs.addTab(self.frequencyConfigTab, "Frequency")
|
|
|
|
self.frequencyLayouts = []
|
|
self.frequencyLines = []
|
|
self.modeBoxes = []
|
|
self.positionPrescalers = []
|
|
self.telemetryPrescalers = []
|
|
|
|
self.modes = ['APRS AFSK 1200', 'APRS AFSK 9600', 'APRS AFSK CUSTOM', 'APRS_LORA 300', 'APRS LORA 1200', 'APRS LORA CUSTOM', '4FSK HORUS V2', 'FM SSTV', 'FM AUDIO']
|
|
prescalers = [str(i) for i in range(0, 16)]
|
|
|
|
self.columnLabelsLayout = QHBoxLayout()
|
|
self.frequencyConfigLayout.addLayout(self.columnLabelsLayout)
|
|
|
|
for i in range(11):
|
|
layout = QHBoxLayout()
|
|
self.frequencyLayouts.append(layout)
|
|
self.frequencyConfigLayout.addLayout(layout)
|
|
|
|
layout.addWidget(QLabel(str(i)+(". " if i < 10 else ".")))
|
|
layout.addWidget(QLabel("Frequency"))
|
|
|
|
frequencyLine = QLineEdit()
|
|
self.frequencyLines.append(frequencyLine)
|
|
layout.addWidget(frequencyLine)
|
|
|
|
layout.addWidget(QLabel("Mode"))
|
|
modeBox = QComboBox()
|
|
self.modeBoxes.append(modeBox)
|
|
modeBox.addItems(self.modes)
|
|
layout.addWidget(modeBox)
|
|
|
|
layout.addWidget(QLabel("Position psc"))
|
|
positionPrescaler = QComboBox()
|
|
self.positionPrescalers.append(positionPrescaler)
|
|
positionPrescaler.addItems(prescalers)
|
|
layout.addWidget(positionPrescaler)
|
|
|
|
layout.addWidget(QLabel("Telemetry psc"))
|
|
telemetryPrescaler = QComboBox()
|
|
self.telemetryPrescalers.append(telemetryPrescaler)
|
|
telemetryPrescaler.addItems(prescalers)
|
|
layout.addWidget(telemetryPrescaler)
|
|
|
|
self.aprsSettings = QGroupBox('APRS Settings')
|
|
self.aprsSettingsLayout = QFormLayout()
|
|
self.aprsSettings.setLayout(self.aprsSettingsLayout)
|
|
|
|
self.aprsCallsign = QLineEdit()
|
|
self.aprsDesignator = QComboBox()
|
|
self.aprsDesignator.addItems([str(i) for i in range(0, 16)])
|
|
self.aprsDigiPath = QLineEdit()
|
|
self.aprsSymbol = QComboBox()
|
|
self.aprsSymbol.addItems(["BALLOON", "CAR", "ROCKET", "WX_STATION", "DIGI"])
|
|
|
|
self.aprsSettingsLayout.addRow('CALLSIGN', self.aprsCallsign)
|
|
self.aprsSettingsLayout.addRow('Designator', self.aprsDesignator)
|
|
self.aprsSettingsLayout.addRow('Digi path', self.aprsDigiPath)
|
|
self.aprsSettingsLayout.addRow('Symbol', self.aprsSymbol)
|
|
self.frequencyConfigTabLayout.addWidget(self.aprsSettings)
|
|
|
|
|
|
def init_sstv_layout(self):
|
|
self.sstvConfigTab = QWidget()
|
|
self.sstvConfigTabLayout = QHBoxLayout()
|
|
self.sstvConfigTab.setLayout(self.sstvConfigTabLayout)
|
|
|
|
self.sstvParametersGroupBox = QGroupBox('SSTV parameters')
|
|
self.sstvParametersGroupBoxLayout = QFormLayout()
|
|
self.sstvParametersGroupBox.setLayout(self.sstvParametersGroupBoxLayout)
|
|
self.sstvConfigTabLayout.addWidget(self.sstvParametersGroupBox)
|
|
|
|
self.sstvTextHeader = QLineEdit()
|
|
self.sstvBHeaderEnabled = QCheckBox()
|
|
self.sstvImageCnt = QComboBox()
|
|
self.sstvImageCnt.addItems([str(i) for i in range(0, 15)])
|
|
self.sstvImageCntPerFreq = QComboBox()
|
|
self.sstvImageCntPerFreq.addItems([str(i) for i in range(0, 15)])
|
|
|
|
self.sstvParametersGroupBoxLayout.addRow('Text header', self.sstvTextHeader)
|
|
self.sstvParametersGroupBoxLayout.addRow('Text header enabled', self.sstvBHeaderEnabled)
|
|
self.sstvParametersGroupBoxLayout.addRow('Images count', self.sstvImageCnt)
|
|
self.sstvParametersGroupBoxLayout.addRow('Images per freq', self.sstvImageCntPerFreq)
|
|
|
|
self.sstvImagesGroupBox = QGroupBox('Images')
|
|
self.sstvImagesGroupBoxLayout = QVBoxLayout()
|
|
self.sstvImagesGroupBox.setLayout(self.sstvImagesGroupBoxLayout)
|
|
self.sstvConfigTabLayout.addWidget(self.sstvImagesGroupBox)
|
|
|
|
self.sstvImagesSelectButton = QPushButton('Open images')
|
|
self.sstvImagesGroupBoxLayout.addWidget(self.sstvImagesSelectButton)
|
|
self.sstvImagesSelectButton.clicked.connect(self.sstv_file_chose_dialog)
|
|
|
|
self.sstvImagesGroupBoxLayout.addWidget(QLabel('Selected images:'))
|
|
self.sstvImagesSelectedList = QListWidget()
|
|
self.sstvImagesGroupBoxLayout.addWidget(self.sstvImagesSelectedList)
|
|
|
|
self.sstvImagesUploadButton = QPushButton('Upload images to device')
|
|
self.sstvImagesGroupBoxLayout.addWidget(self.sstvImagesUploadButton)
|
|
self.sstvImagesUploadButton.pressed.connect(self.init_sstv_upload)
|
|
|
|
self.tabs.addTab(self.sstvConfigTab, "SSTV config")
|
|
|
|
def sstv_file_chose_dialog(self):
|
|
options = QFileDialog.Options()
|
|
options |= QFileDialog.ReadOnly
|
|
self.sstv_files, _ = QFileDialog.getOpenFileNames(self,"Select file", "","PNG (*.png);JPEG (*.jpg);;All Files (*)", options=options)
|
|
|
|
self.sstvImagesSelectedList.clear()
|
|
for image in self.sstv_files:
|
|
thumbnail = QListWidgetItem()
|
|
thumbnail.setText(image)
|
|
thumbnail.setIcon(QIcon(image))
|
|
self.sstvImagesSelectedList.addItem(thumbnail)
|
|
|
|
def init_geoconig_layout(self):
|
|
self.geoConfigTab = QWidget()
|
|
self.geoConfigTabLayout = QVBoxLayout()
|
|
self.geoConfigTab.setLayout(self.geoConfigTabLayout)
|
|
|
|
self.geoConfigRowLayouts = []
|
|
self.geoConfigModeBoxes = []
|
|
self.geoConfigCordLines = []
|
|
|
|
self.positionModes = ['TX INSIDE', 'TX OUTSIDE']
|
|
|
|
for i in range(11):
|
|
RowLayout = QHBoxLayout()
|
|
self.geoConfigRowLayouts.append(RowLayout)
|
|
self.geoConfigTabLayout.addLayout(RowLayout)
|
|
|
|
RowLayout.addWidget(QLabel(str(i)+(". " if i < 10 else ".")))
|
|
|
|
PositionModeBox = QComboBox()
|
|
PositionModeBox.addItems(self.positionModes)
|
|
self.geoConfigModeBoxes.append(PositionModeBox)
|
|
RowLayout.addWidget(PositionModeBox)
|
|
|
|
CoordinatesLinesInRow = []
|
|
for i in range(5):
|
|
CoordLineX = QLineEdit()
|
|
CoordLineY = QLineEdit()
|
|
CoordinatesLinesInRow.append(CoordLineX)
|
|
CoordinatesLinesInRow.append(CoordLineY)
|
|
|
|
RowLayout.addWidget(QLabel("X"+str(i)))
|
|
RowLayout.addWidget(CoordLineX)
|
|
RowLayout.addWidget(QLabel("Y"+str(i)))
|
|
RowLayout.addWidget(CoordLineY)
|
|
|
|
self.geoConfigCordLines.append(CoordinatesLinesInRow)
|
|
|
|
self.tabs.addTab(self.geoConfigTab, "Geofencing")
|
|
|
|
def init_update_layout(self):
|
|
self.fwUpdateTab = QWidget()
|
|
self.fwUpdateTabLayout = QVBoxLayout()
|
|
self.fwUpdateTab.setLayout(self.fwUpdateTabLayout)
|
|
|
|
self.fwUpdateRowLayout = QHBoxLayout()
|
|
self.fwUpdateTabLayout.addLayout(self.fwUpdateRowLayout)
|
|
|
|
self.fwUpdateSelectButton = QPushButton('Select file')
|
|
self.fwUpdateRowLayout.addWidget(self.fwUpdateSelectButton)
|
|
self.fwUpdateSelectButton.clicked.connect(self.load_file_update_dialog)
|
|
|
|
self.fwUpdateFileLabel = QLabel("firmware file not selected")
|
|
self.fwUpdateRowLayout.addWidget(self.fwUpdateFileLabel)
|
|
|
|
self.fwUpdateUploadButton = QPushButton('upload')
|
|
self.fwUpdateUploadButton.clicked.connect(self.init_firmware_upload)
|
|
self.fwUpdateTabLayout.addWidget(self.fwUpdateUploadButton)
|
|
self.fwUpdateTabLayout.addStretch(1)
|
|
|
|
self.tabs.addTab(self.fwUpdateTab, "Software update")
|
|
|
|
def load_file_update_dialog(self):
|
|
options = QFileDialog.Options()
|
|
options |= QFileDialog.ReadOnly
|
|
self.fw_file, _ = QFileDialog.getOpenFileName(self,"Select file", "","BIN (*.bin);;All Files (*)", options=options)
|
|
|
|
if self.fw_file:
|
|
self.fwUpdateFileLabel.setText(f"Wybrane pliki: {self.fw_file}")
|
|
|
|
def init_firmware_upload(self):
|
|
if not self.fw_file:
|
|
return
|
|
|
|
self.disable_buttons(True)
|
|
self.fw_uploader.upload_firmware(self.fw_file, self.progress_callback)
|
|
|
|
def init_sstv_upload(self):
|
|
if not self.sstv_files:
|
|
return
|
|
|
|
self.disable_buttons(True)
|
|
self.img_uploader.upload_images(self.sstv_files, self.progress_callback)
|
|
|
|
# app = QApplication(sys.argv)
|
|
|
|
# widget = SerialConfigurator()
|
|
# widget.show()
|
|
|
|
# sys.exit(app.exec_())
|
|
|