commit accfb19626c6de896213cee57fea1d47cfc94c8a Author: Piotr Dobrowolski Date: Mon Aug 27 18:07:43 2018 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..41598c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.ropeproject +*.py[oc] +*.swp + +# Ignore compiled Qt assets +*.qm +gui/*.py +!gui/__init__.py diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..47ca08e --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +UI_FILES = $(wildcard gui/*.ui) +TS_FILES = $(wildcard i18n/*.ts) +PY_FILES = $(wildcard *.py) $(wildcard gui/*.py) + +UI_COMPILED = $(UI_FILES:.ui=.py) +TS_COMPILED = $(TS_FILES:.ts=.qm) + +%.py: %.ui + pyuic5 $< > $@ + +%.qm: %.ts + lrelease $< + +all: $(UI_COMPILED) $(TS_COMPILED) + +clean: + rm $(UI_COMPILED) + rm $(TS_COMPILED) + +run: all + python3 luftdaten-tool.py + +i18n-update: + @for f in $(TS_FILES) ; do \ + pylupdate5 *.py gui/*.py -ts $$f -verbose; \ + done diff --git a/README.md b/README.md new file mode 100644 index 0000000..8d82cc4 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +luftdaten.info flashing tool +============================ + +Binary builds +------------- + +Our main target is having working prebuilt binaries for users to simply +download and run, to avoid all the setup below. + +Development +----------- + +Both build & runtime requirements are defined in `requirements.txt` file. In +order to install these use the following command: + + pip install -r requirements.txt + +To manage dynamic UI and translation binaries generation we use a very simple +GNU make-based build system. + +To simply build everything needed to run `luftdaten-tool.py` run: + + make + +To build and run use: + + make run + +To remove all build artifacts use: + + make clean + +All requirements are set up using wildcards, so, in theory, `Makefile` shouldn't +need much changes in near future. + +Translations +------------ + +In order to rebuild `*.ts` files use: + + make i18n-update diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000..5ae9b7c Binary files /dev/null and b/assets/logo.png differ diff --git a/gui/__init__.py b/gui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gui/mainwindow.ui b/gui/mainwindow.ui new file mode 100644 index 0000000..096f345 --- /dev/null +++ b/gui/mainwindow.ui @@ -0,0 +1,171 @@ + + + MainWindow + + + + 0 + 0 + 503 + 530 + + + + + 0 + 0 + + + + Luftdaten.info Flashing Tool + + + + ../assets/logo.png../assets/logo.png + + + + + 0 + 0 + + + + + + + + 0 + 0 + + + + + + + Board: + + + + + + + + 0 + 0 + + + + + + + + Firmware version: + + + + + + + + 0 + 0 + + + + true + + + master + + + + + + + + + + + + + 0 + 0 + + + + Upload + + + + + + + Expert mode + + + true + + + + + + + + + + + + + 0 + 0 + + + + + + + Baudrate: + + + + + + + + 0 + 0 + + + + + + + + + 0 + 1 + + + + + + + + + + + + + 0 + 0 + 503 + 29 + + + + + + + + diff --git a/i18n/Polish.ts b/i18n/Polish.ts new file mode 100644 index 0000000..7437ff5 --- /dev/null +++ b/i18n/Polish.ts @@ -0,0 +1,61 @@ + + + + MainWindow + + + No boards found + Nie znaleziono płytki + + + + Others... + Inne... + + + + No device selected. + Nie wybrano urządzenia. + + + + No version selected. + Nie wybrano wersji. + + + + Luftdaten.info Flashing Tool + + + + + Board: + Płytka: + + + + Firmware version: + Wersja oprogramowania: + + + + master + master + + + + Upload + Wgraj + + + + Expert mode + Tryb eksperta + + + + Baudrate: + Prędkość portu: + + + diff --git a/luftdaten-tool.py b/luftdaten-tool.py new file mode 100644 index 0000000..83f31fa --- /dev/null +++ b/luftdaten-tool.py @@ -0,0 +1,109 @@ +# -* encoding: utf-8 *- +import sys +import os.path + +import serial +import serial.tools.list_ports +from PyQt5 import QtGui, QtCore, QtWidgets + +from gui import mainwindow + + +PREFERED_PORTS = [ + # CH341 + (0x1A86, 0x7523), + + # CP2102 + (0x10c4, 0xea60), +] + +ROLE_DEVICE = QtCore.Qt.UserRole + 1 + + +class MainWindow(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): + def __init__(self, parent=None, app=None): + super(MainWindow, self).__init__(parent) + + # FIXME: dirty hack to solve relative paths in *.ui + oldcwd = os.getcwd() + os.chdir('assets') + self.setupUi(self) + os.chdir(oldcwd) + + self.app = app + self.translator = QtCore.QTranslator() + + self.i18n_init(QtCore.QLocale.system()) + self.populate_boards(serial.tools.list_ports.comports()) + + self.on_expertModeBox_clicked() + + def i18n_init(self, locale): + self.app.removeTranslator(self.translator) + self.translator.load(os.path.join('i18n', QtCore.QLocale.languageToString(locale.language())+ '.qm')) + self.app.installTranslator(self.translator) + self.retranslateUi(self) + + def populate_boards(self, ports): + prefered, others = self.group_ports(serial.tools.list_ports.comports()) + + for b in prefered: + item = QtGui.QStandardItem( + '{0.description} ({0.device})'.format(b)) + item.setData(b.device, ROLE_DEVICE) + self.boardBox.model().appendRow(item) + + if not prefered: + sep = QtGui.QStandardItem(self.tr('No boards found')) + sep.setEnabled(False) + self.boardBox.model().appendRow(sep) + + if others: + sep = QtGui.QStandardItem(self.tr('Others...')) + sep.setEnabled(False) + self.boardBox.model().appendRow(sep) + + for b in others: + item = QtGui.QStandardItem( + '{0.description} ({0.device})'.format(b)) + item.setData(b.device, ROLE_DEVICE) + self.boardBox.model().appendRow(item) + + def group_ports(self, ports): + prefered = [] + others = [] + + for p in ports: + if (p.vid, p.pid) in PREFERED_PORTS: + prefered.append(p) + else: + others.append(p) + return prefered, others + + def on_actionExit_triggered(self): + """This handles activation of "Exit" menu action""" + self.app.exit() + + def on_uploadButton_clicked(self): + self.statusbar.clearMessage() + + device = self.boardBox.currentData(ROLE_DEVICE) + version = self.versionBox.currentText() + + if not device: + self.statusbar.showMessage(self.tr("No device selected.")) + return + + if not version: + self.statusbar.showMessage(self.tr("No version selected.")) + return + + def on_expertModeBox_clicked(self): + self.expertForm.setVisible(self.expertModeBox.checkState()) + + +if __name__ == "__main__": + app = QtWidgets.QApplication(sys.argv) + window = MainWindow(app=app) + window.show() + sys.exit(app.exec_()) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2fc9742 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +ecdsa==0.13 +esptool==2.5.0 +pyaes==1.6.1 +PyQt5==5.11.2 +PyQt5-sip==4.19.12 +pyserial==3.4