Compare commits
18 Commits
bda1478039
...
zeroconf
| Author | SHA1 | Date | |
|---|---|---|---|
| ecac90373c | |||
| 1e46161c6a | |||
| a26852bf14 | |||
| f1023ebacb | |||
| 1042835685 | |||
| af335bac94 | |||
| e12f27c1e1 | |||
| 6031cf8213 | |||
| 68a264f9ba | |||
| e4a6983ed9 | |||
| f947a382b7 | |||
| 9a426f25bb | |||
| e302b796ae | |||
| caf09bc432 | |||
| 12988acda9 | |||
| c734a7f5e7 | |||
| d6335e69bb | |||
| df6f0808a3 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -2,6 +2,11 @@
|
|||||||
*.py[oc]
|
*.py[oc]
|
||||||
*.swp
|
*.swp
|
||||||
|
|
||||||
|
_buildid.py
|
||||||
|
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
|
||||||
# Ignore compiled Qt assets
|
# Ignore compiled Qt assets
|
||||||
*.qm
|
*.qm
|
||||||
gui/*.py
|
gui/*.py
|
||||||
|
|||||||
20
LICENSE
Normal file
20
LICENSE
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright 2018 Piotr "inf" Dobrowolski <informatic@hackerspace.pl>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
34
Makefile
34
Makefile
@@ -1,10 +1,17 @@
|
|||||||
UI_FILES = $(wildcard gui/*.ui)
|
UI_FILES = $(wildcard gui/*.ui)
|
||||||
TS_FILES = $(wildcard i18n/*.ts)
|
TS_FILES = $(wildcard i18n/*.ts)
|
||||||
PY_FILES = $(wildcard *.py) $(wildcard gui/*.py)
|
PY_FILES = $(wildcard *.py) $(wildcard gui/*.py) $(wildcard luftdatentool/*.py)
|
||||||
|
|
||||||
UI_COMPILED = $(UI_FILES:.ui=.py)
|
UI_COMPILED = $(UI_FILES:.ui=.py)
|
||||||
TS_COMPILED = $(TS_FILES:.ts=.qm)
|
TS_COMPILED = $(TS_FILES:.ts=.qm)
|
||||||
|
|
||||||
|
# python3.6 on Windows is only available as python.exe
|
||||||
|
ifeq (, $(shell which python3))
|
||||||
|
PY ?= python
|
||||||
|
else
|
||||||
|
PY ?= python3
|
||||||
|
endif
|
||||||
|
|
||||||
%.py: %.ui
|
%.py: %.ui
|
||||||
pyuic5 $< > $@
|
pyuic5 $< > $@
|
||||||
|
|
||||||
@@ -18,9 +25,28 @@ clean:
|
|||||||
rm $(TS_COMPILED)
|
rm $(TS_COMPILED)
|
||||||
|
|
||||||
run: all
|
run: all
|
||||||
python3 luftdaten-tool.py
|
$(PY) luftdaten-tool.py
|
||||||
|
|
||||||
i18n-update:
|
# Updates all translation files in i18n/ directory
|
||||||
|
i18n-update: $(UI_COMPILED)
|
||||||
@for f in $(TS_FILES) ; do \
|
@for f in $(TS_FILES) ; do \
|
||||||
pylupdate5 *.py gui/*.py -ts $$f -verbose; \
|
pylupdate5 $(PY_FILES) -ts $$f -verbose; \
|
||||||
done
|
done
|
||||||
|
|
||||||
|
deps:
|
||||||
|
$(PY) -m pip install -U -r requirements.txt
|
||||||
|
|
||||||
|
# Here go platform-specific buildsteps
|
||||||
|
UNAME_S := $(shell uname -s)
|
||||||
|
|
||||||
|
ifeq ($(UNAME_S),Darwin)
|
||||||
|
PLATFORM_DEPS := assets/logo.icns
|
||||||
|
assets/logo.icns: assets/logo.png
|
||||||
|
deploy/mkicns $<
|
||||||
|
endif
|
||||||
|
|
||||||
|
dist: all $(PLATFORM_DEPS)
|
||||||
|
$(PY) -m PyInstaller -y luftdaten-tool.spec
|
||||||
|
|
||||||
|
dmg: dist
|
||||||
|
dmgbuild -s deploy/dmgbuild_settings.py -D app=dist/Luftdaten.info\ Flashing\ Tool.app "Luftdaten.info Flashing Tool" dist/luftdaten-tool.dmg
|
||||||
|
|||||||
29
README.md
29
README.md
@@ -7,6 +7,35 @@ Binary builds
|
|||||||
Our main target is having working prebuilt binaries for users to simply
|
Our main target is having working prebuilt binaries for users to simply
|
||||||
download and run, to avoid all the setup below.
|
download and run, to avoid all the setup below.
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
Currently Linux builds require *Python 3.6* (but 3.7 seems to work fine as
|
||||||
|
well), GNU make and Qt Linguist tools. Following packages should suffice on
|
||||||
|
Ubuntu:
|
||||||
|
|
||||||
|
sudo apt install qttools5-dev-tools python3.6 make
|
||||||
|
|
||||||
|
Then, to install python dependencies and build the binary use:
|
||||||
|
|
||||||
|
make deps dist
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
Currently Windows builds require *Python 3.6* installed system-wide and added to
|
||||||
|
`%PATH%`.
|
||||||
|
|
||||||
|
To install python and cygwin dependencies and build everything use
|
||||||
|
`deploy\windows-build.bat` batch script.
|
||||||
|
|
||||||
|
### MacOS
|
||||||
|
Currently MacOS builds require *Python 3.6* and Qt SDK installed (just the "Qt >
|
||||||
|
5... > macOS" part in installer) with following added to $PATH:
|
||||||
|
|
||||||
|
export PATH="$HOME/Qt/5.11.1/clang_64/bin:$PATH"
|
||||||
|
|
||||||
|
Then just install dependencies and build everything using:
|
||||||
|
|
||||||
|
make deps dmg
|
||||||
|
|
||||||
Development
|
Development
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
|||||||
BIN
assets/logo.ico
Normal file
BIN
assets/logo.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
assets/logo.png
BIN
assets/logo.png
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 18 KiB |
174
deploy/dmgbuild_settings.py
Normal file
174
deploy/dmgbuild_settings.py
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import biplist
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
#
|
||||||
|
# Example settings file for dmgbuild
|
||||||
|
#
|
||||||
|
|
||||||
|
# Use like this: dmgbuild -s settings.py "Test Volume" test.dmg
|
||||||
|
|
||||||
|
# You can actually use this file for your own application (not just TextEdit)
|
||||||
|
# by doing e.g.
|
||||||
|
#
|
||||||
|
# dmgbuild -s settings.py -D app=/path/to/My.app "My Application" MyApp.dmg
|
||||||
|
|
||||||
|
# .. Useful stuff ..............................................................
|
||||||
|
|
||||||
|
application = defines.get('app', '/Applications/TextEdit.app')
|
||||||
|
appname = os.path.basename(application)
|
||||||
|
|
||||||
|
def icon_from_app(app_path):
|
||||||
|
plist_path = os.path.join(app_path, 'Contents', 'Info.plist')
|
||||||
|
plist = biplist.readPlist(plist_path)
|
||||||
|
icon_name = plist['CFBundleIconFile']
|
||||||
|
icon_root,icon_ext = os.path.splitext(icon_name)
|
||||||
|
if not icon_ext:
|
||||||
|
icon_ext = '.icns'
|
||||||
|
icon_name = icon_root + icon_ext
|
||||||
|
return os.path.join(app_path, 'Contents', 'Resources', icon_name)
|
||||||
|
|
||||||
|
# .. Basics ....................................................................
|
||||||
|
|
||||||
|
# Uncomment to override the output filename
|
||||||
|
# filename = 'test.dmg'
|
||||||
|
|
||||||
|
# Uncomment to override the output volume name
|
||||||
|
# volume_name = 'Test'
|
||||||
|
|
||||||
|
# Volume format (see hdiutil create -help)
|
||||||
|
format = defines.get('format', 'UDBZ')
|
||||||
|
|
||||||
|
# Volume size (must be large enough for your files)
|
||||||
|
size = defines.get('size', '100M')
|
||||||
|
|
||||||
|
# Files to include
|
||||||
|
files = [ application ]
|
||||||
|
|
||||||
|
# Symlinks to create
|
||||||
|
symlinks = { 'Applications': '/Applications' }
|
||||||
|
|
||||||
|
# Volume icon
|
||||||
|
#
|
||||||
|
# You can either define icon, in which case that icon file will be copied to the
|
||||||
|
# image, *or* you can define badge_icon, in which case the icon file you specify
|
||||||
|
# will be used to badge the system's Removable Disk icon
|
||||||
|
#
|
||||||
|
#icon = '/path/to/icon.icns'
|
||||||
|
badge_icon = icon_from_app(application)
|
||||||
|
|
||||||
|
# Where to put the icons
|
||||||
|
icon_locations = {
|
||||||
|
appname: (140, 120),
|
||||||
|
'Applications': (500, 120)
|
||||||
|
}
|
||||||
|
|
||||||
|
# .. Window configuration ......................................................
|
||||||
|
|
||||||
|
# Background
|
||||||
|
#
|
||||||
|
# This is a STRING containing any of the following:
|
||||||
|
#
|
||||||
|
# #3344ff - web-style RGB color
|
||||||
|
# #34f - web-style RGB color, short form (#34f == #3344ff)
|
||||||
|
# rgb(1,0,0) - RGB color, each value is between 0 and 1
|
||||||
|
# hsl(120,1,.5) - HSL (hue saturation lightness) color
|
||||||
|
# hwb(300,0,0) - HWB (hue whiteness blackness) color
|
||||||
|
# cmyk(0,1,0,0) - CMYK color
|
||||||
|
# goldenrod - X11/SVG named color
|
||||||
|
# builtin-arrow - A simple built-in background with a blue arrow
|
||||||
|
# /foo/bar/baz.png - The path to an image file
|
||||||
|
#
|
||||||
|
# The hue component in hsl() and hwb() may include a unit; it defaults to
|
||||||
|
# degrees ('deg'), but also supports radians ('rad') and gradians ('grad'
|
||||||
|
# or 'gon').
|
||||||
|
#
|
||||||
|
# Other color components may be expressed either in the range 0 to 1, or
|
||||||
|
# as percentages (e.g. 60% is equivalent to 0.6).
|
||||||
|
background = 'builtin-arrow'
|
||||||
|
|
||||||
|
show_status_bar = False
|
||||||
|
show_tab_view = False
|
||||||
|
show_toolbar = False
|
||||||
|
show_pathbar = False
|
||||||
|
show_sidebar = False
|
||||||
|
sidebar_width = 180
|
||||||
|
|
||||||
|
# Window position in ((x, y), (w, h)) format
|
||||||
|
window_rect = ((100, 100), (640, 280))
|
||||||
|
|
||||||
|
# Select the default view; must be one of
|
||||||
|
#
|
||||||
|
# 'icon-view'
|
||||||
|
# 'list-view'
|
||||||
|
# 'column-view'
|
||||||
|
# 'coverflow'
|
||||||
|
#
|
||||||
|
default_view = 'icon-view'
|
||||||
|
|
||||||
|
# General view configuration
|
||||||
|
show_icon_preview = False
|
||||||
|
|
||||||
|
# Set these to True to force inclusion of icon/list view settings (otherwise
|
||||||
|
# we only include settings for the default view)
|
||||||
|
include_icon_view_settings = 'auto'
|
||||||
|
include_list_view_settings = 'auto'
|
||||||
|
|
||||||
|
# .. Icon view configuration ...................................................
|
||||||
|
|
||||||
|
arrange_by = None
|
||||||
|
grid_offset = (0, 0)
|
||||||
|
grid_spacing = 100
|
||||||
|
scroll_position = (0, 0)
|
||||||
|
label_pos = 'bottom' # or 'right'
|
||||||
|
text_size = 16
|
||||||
|
icon_size = 128
|
||||||
|
|
||||||
|
# .. List view configuration ...................................................
|
||||||
|
|
||||||
|
# Column names are as follows:
|
||||||
|
#
|
||||||
|
# name
|
||||||
|
# date-modified
|
||||||
|
# date-created
|
||||||
|
# date-added
|
||||||
|
# date-last-opened
|
||||||
|
# size
|
||||||
|
# kind
|
||||||
|
# label
|
||||||
|
# version
|
||||||
|
# comments
|
||||||
|
#
|
||||||
|
list_icon_size = 16
|
||||||
|
list_text_size = 12
|
||||||
|
list_scroll_position = (0, 0)
|
||||||
|
list_sort_by = 'name'
|
||||||
|
list_use_relative_dates = True
|
||||||
|
list_calculate_all_sizes = False,
|
||||||
|
list_columns = ('name', 'date-modified', 'size', 'kind', 'date-added')
|
||||||
|
list_column_widths = {
|
||||||
|
'name': 300,
|
||||||
|
'date-modified': 181,
|
||||||
|
'date-created': 181,
|
||||||
|
'date-added': 181,
|
||||||
|
'date-last-opened': 181,
|
||||||
|
'size': 97,
|
||||||
|
'kind': 115,
|
||||||
|
'label': 100,
|
||||||
|
'version': 75,
|
||||||
|
'comments': 300,
|
||||||
|
}
|
||||||
|
list_column_sort_directions = {
|
||||||
|
'name': 'ascending',
|
||||||
|
'date-modified': 'descending',
|
||||||
|
'date-created': 'descending',
|
||||||
|
'date-added': 'descending',
|
||||||
|
'date-last-opened': 'descending',
|
||||||
|
'size': 'descending',
|
||||||
|
'kind': 'ascending',
|
||||||
|
'label': 'ascending',
|
||||||
|
'version': 'ascending',
|
||||||
|
'comments': 'ascending',
|
||||||
|
}
|
||||||
20
deploy/mkicns
Executable file
20
deploy/mkicns
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Builds .icns file from a single .png file
|
||||||
|
#
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
filename="${1%.*}"
|
||||||
|
mkdir "$filename".iconset
|
||||||
|
for i in 16 32 128 256 ; do
|
||||||
|
n=$(( i * 2 ))
|
||||||
|
sips -z $i $i "$1" --out "$filename".iconset/icon_${i}x${i}.png
|
||||||
|
sips -z $n $n "$1" --out "$filename".iconset/icon_${i}x${i}@2x.png
|
||||||
|
[[ $n -eq 512 ]] && \
|
||||||
|
sips -z $n $n "$1" --out "$filename".iconset/icon_${n}x${n}.png
|
||||||
|
(( i++ ))
|
||||||
|
done
|
||||||
|
cp "$1" "$filename".iconset/icon_512x512@2x.png
|
||||||
|
iconutil -c icns "$filename".iconset
|
||||||
|
rm -r "$filename".iconset
|
||||||
21
deploy/windows-build.bat
Normal file
21
deploy/windows-build.bat
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
cd %~dp0\..
|
||||||
|
|
||||||
|
if not exist build mkdir build
|
||||||
|
|
||||||
|
rem Download cygwin installer
|
||||||
|
if not exist build\cygwin-x86.exe powershell -Command "Invoke-WebRequest https://cygwin.com/setup-x86.exe -OutFile build\cygwin-x86.exe"
|
||||||
|
|
||||||
|
rem Install required Cygwin packages
|
||||||
|
if not exist build\cygwin build\cygwin-x86.exe --site http://cygwin.mirror.constant.com ^
|
||||||
|
--no-shortcuts ^
|
||||||
|
--no-desktop ^
|
||||||
|
--quiet-mode ^
|
||||||
|
--root "%cd%\build\cygwin" ^
|
||||||
|
--arch x86 ^
|
||||||
|
--local-package-dir "%cd%\build\cygwin-packages" ^
|
||||||
|
--verbose ^
|
||||||
|
--prune-install ^
|
||||||
|
--no-admin ^
|
||||||
|
--packages qt5-linguist-tools,make
|
||||||
|
|
||||||
|
build\cygwin\bin\bash.exe --login -i -c "ln -s `which lrelease-qt5` /usr/bin/lrelease ; cd \"%cd%\" && make deps dist"
|
||||||
@@ -6,8 +6,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>503</width>
|
<width>493</width>
|
||||||
<height>530</height>
|
<height>466</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Luftdaten.info Flashing Tool</string>
|
<string>Luftdaten.info Flashing Tool (v{version})</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowIcon">
|
<property name="windowIcon">
|
||||||
<iconset>
|
<iconset>
|
||||||
@@ -32,132 +32,306 @@
|
|||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QWidget" name="formWidget" native="true">
|
<widget class="QFrame" name="globalMessage">
|
||||||
<property name="sizePolicy">
|
<property name="frameShape">
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
|
<enum>QFrame::StyledPanel</enum>
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
</property>
|
||||||
<layout class="QFormLayout" name="formLayout">
|
<property name="frameShadow">
|
||||||
<item row="0" column="0">
|
<enum>QFrame::Raised</enum>
|
||||||
<widget class="QLabel" name="label">
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="globalMessageTitle">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Board:</string>
|
<string/>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="1">
|
<item>
|
||||||
<widget class="QComboBox" name="boardBox">
|
<widget class="QLabel" name="globalMessageText">
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QLabel" name="label_2">
|
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Firmware version:</string>
|
<string/>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
<property name="openExternalLinks">
|
||||||
</item>
|
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="QComboBox" name="versionBox">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="editable">
|
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
<property name="currentText">
|
|
||||||
<string>master</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0" colspan="2">
|
|
||||||
<widget class="QProgressBar" name="progressBar"/>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="0" colspan="2">
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="uploadButton">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="font">
|
|
||||||
<font>
|
|
||||||
<pointsize>11</pointsize>
|
|
||||||
</font>
|
|
||||||
</property>
|
|
||||||
<property name="styleSheet">
|
|
||||||
<string notr="true">padding:10px</string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Upload</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QCheckBox" name="expertModeBox">
|
|
||||||
<property name="text">
|
|
||||||
<string>Expert mode</string>
|
|
||||||
</property>
|
|
||||||
<property name="checkable">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QWidget" name="expertForm" native="true">
|
<widget class="QTabWidget" name="tabWidget">
|
||||||
<property name="sizePolicy">
|
<property name="layoutDirection">
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
<enum>Qt::LeftToRight</enum>
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
</property>
|
||||||
<layout class="QFormLayout" name="formLayout_2">
|
<property name="tabPosition">
|
||||||
<item row="0" column="0">
|
<enum>QTabWidget::South</enum>
|
||||||
<widget class="QLabel" name="label_3">
|
</property>
|
||||||
<property name="text">
|
<property name="currentIndex">
|
||||||
<string>Baudrate:</string>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
<widget class="QWidget" name="programmingTab">
|
||||||
</item>
|
<property name="sizePolicy">
|
||||||
<item row="0" column="1">
|
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||||
<widget class="QComboBox" name="comboBox">
|
<horstretch>0</horstretch>
|
||||||
<property name="sizePolicy">
|
<verstretch>0</verstretch>
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
|
</sizepolicy>
|
||||||
<horstretch>0</horstretch>
|
</property>
|
||||||
<verstretch>0</verstretch>
|
<attribute name="title">
|
||||||
</sizepolicy>
|
<string>Flashing</string>
|
||||||
</property>
|
</attribute>
|
||||||
</widget>
|
<layout class="QGridLayout" name="gridLayout_2" rowstretch="0,0,0,0,0,0,0,1">
|
||||||
</item>
|
<item row="3" column="0">
|
||||||
<item row="1" column="0" colspan="2">
|
<widget class="QLabel" name="label_2">
|
||||||
<widget class="QTextBrowser" name="textBrowser">
|
<property name="text">
|
||||||
<property name="sizePolicy">
|
<string>Firmware version:</string>
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
</property>
|
||||||
<horstretch>0</horstretch>
|
</widget>
|
||||||
<verstretch>1</verstretch>
|
</item>
|
||||||
</sizepolicy>
|
<item row="5" column="0" colspan="2">
|
||||||
</property>
|
<widget class="QProgressBar" name="progressBar"/>
|
||||||
</widget>
|
</item>
|
||||||
</item>
|
<item row="3" column="1">
|
||||||
</layout>
|
<widget class="QComboBox" name="versionBox">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="editable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0" colspan="2">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="uploadButton">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Upload</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="expertModeBox">
|
||||||
|
<property name="text">
|
||||||
|
<string>Expert mode</string>
|
||||||
|
</property>
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="boardBox">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Board:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="0" colspan="2">
|
||||||
|
<widget class="QWidget" name="expertForm" native="true">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<layout class="QFormLayout" name="formLayout_2">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Baudrate:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="comboBox">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0" colspan="2">
|
||||||
|
<widget class="QTextBrowser" name="textBrowser">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>1</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="discoveryTab">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Discovery</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<item row="3" column="0" colspan="2">
|
||||||
|
<widget class="QListWidget" name="discoveryList"/>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0" colspan="2">
|
||||||
|
<widget class="QLabel" name="label_5">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<italic>true</italic>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Double-click to open configuration page.</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Sensors detected in local network:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QPushButton" name="discoveryRefreshButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Refresh</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="serialTab">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Serial Monitor</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_4">
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QTextEdit" name="serialTextEdit"/>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label_9">
|
||||||
|
<property name="text">
|
||||||
|
<string>In case of sensor issues, Serial Monitor can be used to review logs sent by the sensor over USB cable.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QPushButton" name="serialConnectButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Connect</string>
|
||||||
|
</property>
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="aboutTab">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>About</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_3">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>12</number>
|
||||||
|
</property>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QLabel" name="buildLabel">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>50</weight>
|
||||||
|
<bold>false</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string><b>Luftdaten.info Flashing Tool</b><br/>Build {build_id}</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_6">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>64</width>
|
||||||
|
<height>64</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="pixmap">
|
||||||
|
<pixmap>../assets/logo.png</pixmap>
|
||||||
|
</property>
|
||||||
|
<property name="scaledContents">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0" colspan="2">
|
||||||
|
<widget class="QLabel" name="label_8">
|
||||||
|
<property name="text">
|
||||||
|
<string><html><head/><body><p>Developed by <a href="https://inf.re/"><span style=" text-decoration: underline; color:#0000ff;">Piotr Dobrowolski</span></a>.</p><p>This software is released under the terms of MIT license. No warranty is provided.</p><p>For newest release see: <a href="https://d.inf.re/luftdaten/"><span style=" text-decoration: underline; color:#0000ff;">https://d.inf.re/luftdaten/</span></a></p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="textFormat">
|
||||||
|
<enum>Qt::RichText</enum>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="openExternalLinks">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
@@ -167,8 +341,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>503</width>
|
<width>493</width>
|
||||||
<height>29</height>
|
<height>20</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
|
|||||||
119
i18n/Polish.ts
119
i18n/Polish.ts
@@ -4,94 +4,149 @@
|
|||||||
<context>
|
<context>
|
||||||
<name>MainWindow</name>
|
<name>MainWindow</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../luftdaten-tool.py" line="138"/>
|
<location filename="../luftdaten-tool.py" line="132"/>
|
||||||
<source>No boards found</source>
|
<source>No boards found</source>
|
||||||
<translation>Nie znaleziono płytki</translation>
|
<translation>Nie znaleziono płytki</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../luftdaten-tool.py" line="143"/>
|
<location filename="../luftdaten-tool.py" line="141"/>
|
||||||
<source>Others...</source>
|
<source>Others...</source>
|
||||||
<translation>Inne...</translation>
|
<translation>Inne...</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../luftdaten-tool.py" line="172"/>
|
<location filename="../luftdaten-tool.py" line="170"/>
|
||||||
<source>No device selected.</source>
|
<source>No device selected.</source>
|
||||||
<translation>Nie wybrano urządzenia.</translation>
|
<translation>Nie wybrano urządzenia.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../luftdaten-tool.py" line="176"/>
|
<location filename="../luftdaten-tool.py" line="174"/>
|
||||||
<source>No version selected.</source>
|
<source>No version selected.</source>
|
||||||
<translation>Nie wybrano wersji.</translation>
|
<translation>Nie wybrano wersji.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../gui/mainwindow.py" line="131"/>
|
<location filename="../gui/mainwindow.py" line="226"/>
|
||||||
<source>Luftdaten.info Flashing Tool</source>
|
|
||||||
<translation></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<location filename="../gui/mainwindow.py" line="132"/>
|
|
||||||
<source>Board:</source>
|
<source>Board:</source>
|
||||||
<translation>Płytka:</translation>
|
<translation>Płytka:</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../gui/mainwindow.py" line="133"/>
|
<location filename="../gui/mainwindow.py" line="223"/>
|
||||||
<source>Firmware version:</source>
|
<source>Firmware version:</source>
|
||||||
<translation>Wersja oprogramowania:</translation>
|
<translation>Wersja oprogramowania:</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../gui/mainwindow.py" line="134"/>
|
<location filename="../gui/mainwindow.py" line="224"/>
|
||||||
<source>master</source>
|
|
||||||
<translation>master</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<location filename="../gui/mainwindow.py" line="135"/>
|
|
||||||
<source>Upload</source>
|
<source>Upload</source>
|
||||||
<translation>Wgraj</translation>
|
<translation>Wgraj</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../gui/mainwindow.py" line="136"/>
|
<location filename="../gui/mainwindow.py" line="225"/>
|
||||||
<source>Expert mode</source>
|
<source>Expert mode</source>
|
||||||
<translation>Tryb eksperta</translation>
|
<translation>Tryb eksperta</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../gui/mainwindow.py" line="137"/>
|
<location filename="../gui/mainwindow.py" line="227"/>
|
||||||
<source>Baudrate:</source>
|
<source>Baudrate:</source>
|
||||||
<translation>Prędkość portu:</translation>
|
<translation>Prędkość portu:</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../luftdaten-tool.py" line="191"/>
|
<location filename="../luftdaten-tool.py" line="189"/>
|
||||||
<source>Invalid version / file does not exist</source>
|
<source>Invalid version / file does not exist</source>
|
||||||
<translation>Błędna wersja / plik nie istnieje</translation>
|
<translation>Błędna wersja / plik nie istnieje</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../luftdaten-tool.py" line="196"/>
|
<location filename="../luftdaten-tool.py" line="194"/>
|
||||||
<source>Work in progess...</source>
|
<source>Work in progess...</source>
|
||||||
<translation>Praca w toku...</translation>
|
<translation>Praca w toku...</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../luftdaten-tool.py" line="224"/>
|
<location filename="../luftdaten-tool.py" line="221"/>
|
||||||
<source>Downloading...</source>
|
<source>Downloading...</source>
|
||||||
<translation>Pobieranie...</translation>
|
<translation>Pobieranie...</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../luftdaten-tool.py" line="235"/>
|
<location filename="../luftdaten-tool.py" line="231"/>
|
||||||
<source>Connecting...</source>
|
<source>Connecting...</source>
|
||||||
<translation>Łączenie...</translation>
|
<translation>Łączenie...</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../luftdaten-tool.py" line="240"/>
|
<location filename="../luftdaten-tool.py" line="236"/>
|
||||||
<source>Done. Chip type: %s</source>
|
<source>Connected. Chip type: {chip_type}</source>
|
||||||
<translation>Zrobione. Typ układu: %s</translation>
|
<translation>Połączono. Typ układu: {chip_type}</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../luftdaten-tool.py" line="258"/>
|
<location filename="../luftdaten-tool.py" line="254"/>
|
||||||
<source>Writing at 0x%08x...</source>
|
<source>Writing at 0x{address:08x}...</source>
|
||||||
<translation>Zapisywanie pod adresem 0x%08x...</translation>
|
<translation>Zapisywanie pod adresem 0x{address:08x}...</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../luftdaten-tool.py" line="268"/>
|
<location filename="../luftdaten-tool.py" line="265"/>
|
||||||
<source>Finished in %.2f seconds</source>
|
<source>Finished in {time:.2f} seconds. Sensor ID: {sensor_id}</source>
|
||||||
<translation>Zrobione w %.2f sekundy</translation>
|
<translation>Zakończono w {time:.2f} sekundy. ID sensora: {sensor_id}</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../gui/mainwindow.py" line="236"/>
|
||||||
|
<source>Luftdaten.info Flashing Tool</source>
|
||||||
|
<translation>Luftdaten.info Flashing Tool</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../luftdaten-tool.py" line="52"/>
|
||||||
|
<source>Loading firmware list...</source>
|
||||||
|
<translation>Ładowanie wersji oprogramowania...</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../luftdaten-tool.py" line="132"/>
|
||||||
|
<source>Have you installed <a href="{drivers_url}">the drivers</a>?</source>
|
||||||
|
<translation>Czy zainstalowałeś <a href="{drivers_url}">sterowniki</a>?</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../gui/mainwindow.py" line="228"/>
|
||||||
|
<source>Flashing</source>
|
||||||
|
<translation>Programowanie</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../gui/mainwindow.py" line="229"/>
|
||||||
|
<source>Double-click to open configuration page.</source>
|
||||||
|
<translation>Naciśnij dwukrotnie aby otworzyć stronę konfiguracji.</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../gui/mainwindow.py" line="230"/>
|
||||||
|
<source>Sensors detected in local network:</source>
|
||||||
|
<translation>Sensory wykryte w sieci lokalnej:</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../gui/mainwindow.py" line="231"/>
|
||||||
|
<source>Refresh</source>
|
||||||
|
<translation>Odśwież</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../gui/mainwindow.py" line="232"/>
|
||||||
|
<source>Discovery</source>
|
||||||
|
<translation>Wykrywanie</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../gui/mainwindow.py" line="233"/>
|
||||||
|
<source>In case of sensor issues, Serial Monitor can be used to review logs sent by the sensor over USB cable.</source>
|
||||||
|
<translation>W przypadku problemów z sensorem, Serial Monitor pozwala na przejrzenie stanu urządzenia przez kabel USB.</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../gui/mainwindow.py" line="234"/>
|
||||||
|
<source>Connect</source>
|
||||||
|
<translation>Połącz</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../gui/mainwindow.py" line="235"/>
|
||||||
|
<source>Serial Monitor</source>
|
||||||
|
<translation>Serial Monitor</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../gui/mainwindow.py" line="237"/>
|
||||||
|
<source><html><head/><body><p>Developed by <a href="https://inf.re/"><span style=" text-decoration: underline; color:#0000ff;">Piotr Dobrowolski</span></a>.</p><p>This software is released under the terms of MIT license. No warranty is provided.</p><p>For newest release see: <a href="https://d.inf.re/luftdaten/"><span style=" text-decoration: underline; color:#0000ff;">https://d.inf.re/luftdaten/</span></a></p></body></html></source>
|
||||||
|
<translation><html><head/><body><p>Wykonane przez <a href="https://inf.re/"><span style=" text-decoration: underline; color:#0000ff;">Piotr Dobrowolski</span></a>.</p><p>Ten program wypuszczony jest na warunkach licencji MIT. Brak gwarancji.</p><p>Najnowszą wersję znajdziesz na: <a href="https://d.inf.re/luftdaten/"><span style=" text-decoration: underline; color:#0000ff;">https://d.inf.re/luftdaten/</span></a></p></body></html></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../gui/mainwindow.py" line="238"/>
|
||||||
|
<source>About</source>
|
||||||
|
<translation>O programie</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
</TS>
|
</TS>
|
||||||
|
|||||||
@@ -1,100 +1,86 @@
|
|||||||
# -* encoding: utf-8 *-
|
# -* encoding: utf-8 *-
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
|
||||||
import time
|
import time
|
||||||
import tempfile
|
import tempfile
|
||||||
import hashlib
|
import hashlib
|
||||||
import zlib
|
import zlib
|
||||||
|
import logging
|
||||||
|
|
||||||
import serial
|
|
||||||
import serial.tools.list_ports
|
|
||||||
import requests
|
import requests
|
||||||
from esptool import ESPLoader
|
from esptool import ESPLoader
|
||||||
from PyQt5 import QtGui, QtCore, QtWidgets
|
|
||||||
|
import luftdatentool
|
||||||
|
from luftdatentool.qtvariant import QtGui, QtCore, QtWidgets
|
||||||
|
from luftdatentool.utils import QuickThread
|
||||||
|
from luftdatentool.workers import PortDetectThread, FirmwareListThread, \
|
||||||
|
ZeroconfDiscoveryThread
|
||||||
|
|
||||||
from gui import mainwindow
|
from gui import mainwindow
|
||||||
|
|
||||||
# Firmware update repository
|
from luftdatentool.consts import UPDATE_REPOSITORY, ALLOWED_PROTO, \
|
||||||
UPDATE_REPOSITORY = 'https://www.madavi.de/sensor/update/data/'
|
PREFERED_PORTS, ROLE_DEVICE, DRIVERS_URL
|
||||||
|
|
||||||
# URI prefixes (protocol parts, essentially) to be downloaded using requests
|
if getattr(sys, 'frozen', False):
|
||||||
ALLOWED_PROTO = ('http://', 'https://')
|
RESOURCES_PATH = sys._MEIPASS
|
||||||
|
else:
|
||||||
# vid/pid pairs of known NodeMCU/ESP8266 development boards
|
RESOURCES_PATH = os.path.dirname(os.path.realpath(__file__))
|
||||||
PREFERED_PORTS = [
|
|
||||||
# CH341
|
|
||||||
(0x1A86, 0x7523),
|
|
||||||
|
|
||||||
# CP2102
|
|
||||||
(0x10c4, 0xea60),
|
|
||||||
]
|
|
||||||
|
|
||||||
ROLE_DEVICE = QtCore.Qt.UserRole + 1
|
|
||||||
|
|
||||||
# FIXME move this into something like qtvariant.py
|
|
||||||
QtCore.Signal = QtCore.pyqtSignal
|
|
||||||
QtCore.Slot = QtCore.pyqtSlot
|
|
||||||
|
|
||||||
file_index_re = re.compile(r'<a href="([^"]*)">([^<]*)</a>')
|
|
||||||
|
|
||||||
|
|
||||||
def indexof(path):
|
|
||||||
"""Returns list of filenames parsed off "Index of" page"""
|
|
||||||
resp = requests.get(path)
|
|
||||||
return [a for a, b in file_index_re.findall(resp.text) if a == b]
|
|
||||||
|
|
||||||
|
|
||||||
class QThread(QtCore.QThread):
|
|
||||||
"""Provides similar API to threading.Thread but with additional error
|
|
||||||
reporting based on Qt Signals"""
|
|
||||||
def __init__(self, parent=None, target=None, args=None, kwargs=None,
|
|
||||||
error=None):
|
|
||||||
super(QThread, self).__init__(parent)
|
|
||||||
self.target = target
|
|
||||||
self.args = args or []
|
|
||||||
self.kwargs = kwargs or {}
|
|
||||||
self.error = error
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
try:
|
|
||||||
self.target(*self.args, **self.kwargs)
|
|
||||||
except Exception as exc:
|
|
||||||
if self.error:
|
|
||||||
self.error.emit(str(exc))
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
class MainWindow(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||||
signal = QtCore.Signal([str, int])
|
uploadProgress = QtCore.Signal([str, int])
|
||||||
errorSignal = QtCore.Signal([str])
|
errorSignal = QtCore.Signal([str])
|
||||||
uploadThread = None
|
uploadThread = None
|
||||||
|
zeroconf_discovery = None
|
||||||
|
boards_detected = False
|
||||||
|
|
||||||
def __init__(self, parent=None, app=None):
|
def __init__(self, parent=None, app=None):
|
||||||
super(MainWindow, self).__init__(parent)
|
super(MainWindow, self).__init__(parent)
|
||||||
|
self.setWindowFlags(QtCore.Qt.Dialog)
|
||||||
|
|
||||||
# FIXME: dirty hack to solve relative paths in *.ui
|
# FIXME: dirty hack to solve relative paths in *.ui
|
||||||
oldcwd = os.getcwd()
|
oldcwd = os.getcwd()
|
||||||
os.chdir('assets')
|
os.chdir(os.path.join(RESOURCES_PATH, 'assets'))
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
os.chdir(oldcwd)
|
os.chdir(oldcwd)
|
||||||
|
|
||||||
self.app = app
|
self.app = app
|
||||||
self.translator = QtCore.QTranslator()
|
|
||||||
|
|
||||||
|
self.translator = QtCore.QTranslator()
|
||||||
self.i18n_init(QtCore.QLocale.system())
|
self.i18n_init(QtCore.QLocale.system())
|
||||||
|
|
||||||
# TODO: extract this to separate thread
|
self.statusbar.showMessage(self.tr("Loading firmware list..."))
|
||||||
self.populate_versions()
|
|
||||||
self.populate_boards(serial.tools.list_ports.comports())
|
|
||||||
|
|
||||||
|
self.firmware_list = FirmwareListThread()
|
||||||
|
self.firmware_list.listLoaded.connect(self.populate_versions)
|
||||||
|
self.firmware_list.error.connect(self.on_work_error)
|
||||||
|
self.firmware_list.start()
|
||||||
|
|
||||||
|
self.port_detect = PortDetectThread()
|
||||||
|
self.port_detect.portsUpdate.connect(self.populate_boards)
|
||||||
|
self.port_detect.error.connect(self.on_work_error)
|
||||||
|
self.port_detect.start()
|
||||||
|
|
||||||
|
self.discovery_start()
|
||||||
|
|
||||||
|
self.globalMessage.hide()
|
||||||
|
|
||||||
|
# Hide WIP GUI parts...
|
||||||
self.on_expertModeBox_clicked()
|
self.on_expertModeBox_clicked()
|
||||||
|
self.expertModeBox.hide()
|
||||||
|
self.tabWidget.removeTab(self.tabWidget.indexOf(self.serialTab))
|
||||||
|
|
||||||
self.signal.connect(self.on_work_update)
|
self.uploadProgress.connect(self.on_work_update)
|
||||||
self.errorSignal.connect(self.on_work_error)
|
self.errorSignal.connect(self.on_work_error)
|
||||||
|
|
||||||
self.cachedir = tempfile.TemporaryDirectory()
|
self.cachedir = tempfile.TemporaryDirectory()
|
||||||
|
|
||||||
|
def show_global_message(self, title, message):
|
||||||
|
self.globalMessage.show()
|
||||||
|
self.globalMessageTitle.setText(title)
|
||||||
|
self.globalMessageText.setText(message)
|
||||||
|
|
||||||
def on_work_update(self, status, progress):
|
def on_work_update(self, status, progress):
|
||||||
self.statusbar.showMessage(status)
|
self.statusbar.showMessage(status)
|
||||||
self.progressBar.setValue(progress)
|
self.progressBar.setValue(progress)
|
||||||
@@ -102,19 +88,42 @@ class MainWindow(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
def on_work_error(self, message):
|
def on_work_error(self, message):
|
||||||
self.statusbar.showMessage(message)
|
self.statusbar.showMessage(message)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def version(self):
|
||||||
|
return luftdatentool.__version__
|
||||||
|
|
||||||
|
@property
|
||||||
|
def build_id(self):
|
||||||
|
try:
|
||||||
|
from luftdatentool._buildid import commit, builddate
|
||||||
|
except ImportError:
|
||||||
|
import datetime
|
||||||
|
commit = 'devel'
|
||||||
|
builddate = datetime.datetime.now().strftime('%Y%m%d')
|
||||||
|
|
||||||
|
return '{}-{}/{}'.format(self.version, commit, builddate)
|
||||||
|
|
||||||
def i18n_init(self, locale):
|
def i18n_init(self, locale):
|
||||||
"""Initializes i18n to specified QLocale"""
|
"""Initializes i18n to specified QLocale"""
|
||||||
|
|
||||||
self.app.removeTranslator(self.translator)
|
self.app.removeTranslator(self.translator)
|
||||||
lang = QtCore.QLocale.languageToString(locale.language())
|
lang = QtCore.QLocale.languageToString(locale.language())
|
||||||
self.translator.load(os.path.join('i18n', lang + '.qm'))
|
self.translator.load(os.path.join(
|
||||||
|
RESOURCES_PATH, 'i18n', lang + '.qm'))
|
||||||
self.app.installTranslator(self.translator)
|
self.app.installTranslator(self.translator)
|
||||||
self.retranslateUi(self)
|
self.retranslateUi(self)
|
||||||
|
|
||||||
def populate_versions(self):
|
def retranslateUi(self, win):
|
||||||
|
super(MainWindow, self).retranslateUi(win)
|
||||||
|
|
||||||
|
win.setWindowTitle(win.windowTitle().format(
|
||||||
|
version=self.version))
|
||||||
|
win.buildLabel.setText(win.buildLabel.text().format(
|
||||||
|
build_id=self.build_id))
|
||||||
|
def populate_versions(self, files):
|
||||||
"""Loads available firmware versions into versionbox widget"""
|
"""Loads available firmware versions into versionbox widget"""
|
||||||
|
|
||||||
for fname in indexof(UPDATE_REPOSITORY):
|
for fname in files:
|
||||||
if not fname.endswith('.bin'):
|
if not fname.endswith('.bin'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -122,10 +131,14 @@ class MainWindow(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
item.setData(UPDATE_REPOSITORY + fname, ROLE_DEVICE)
|
item.setData(UPDATE_REPOSITORY + fname, ROLE_DEVICE)
|
||||||
self.versionBox.model().appendRow(item)
|
self.versionBox.model().appendRow(item)
|
||||||
|
|
||||||
|
self.statusbar.clearMessage()
|
||||||
|
|
||||||
def populate_boards(self, ports):
|
def populate_boards(self, ports):
|
||||||
"""Populates board selection combobox from list of pyserial
|
"""Populates board selection combobox from list of pyserial
|
||||||
ListPortInfo objects"""
|
ListPortInfo objects"""
|
||||||
|
|
||||||
|
self.boardBox.clear()
|
||||||
|
|
||||||
prefered, others = self.group_ports(ports)
|
prefered, others = self.group_ports(ports)
|
||||||
|
|
||||||
for b in prefered:
|
for b in prefered:
|
||||||
@@ -139,6 +152,17 @@ class MainWindow(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
sep.setEnabled(False)
|
sep.setEnabled(False)
|
||||||
self.boardBox.model().appendRow(sep)
|
self.boardBox.model().appendRow(sep)
|
||||||
|
|
||||||
|
# No prefered boards has been found so far and there is a
|
||||||
|
# suggested driver download URL available
|
||||||
|
if not self.boards_detected and DRIVERS_URL:
|
||||||
|
self.show_global_message(
|
||||||
|
self.tr('No boards found'),
|
||||||
|
self.tr('Have you installed <a href="{drivers_url}">'
|
||||||
|
'the drivers</a>?').format(drivers_url=DRIVERS_URL))
|
||||||
|
else:
|
||||||
|
self.globalMessage.hide()
|
||||||
|
self.boards_detected = True
|
||||||
|
|
||||||
if others:
|
if others:
|
||||||
sep = QtGui.QStandardItem(self.tr('Others...'))
|
sep = QtGui.QStandardItem(self.tr('Others...'))
|
||||||
sep.setEnabled(False)
|
sep.setEnabled(False)
|
||||||
@@ -192,13 +216,12 @@ class MainWindow(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
"Invalid version / file does not exist"))
|
"Invalid version / file does not exist"))
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.uploadThread and self.uploadThread.isRunning():
|
if self.flash_board.running():
|
||||||
self.statusbar.showMessage(self.tr("Work in progess..."))
|
self.statusbar.showMessage(self.tr("Work in progess..."))
|
||||||
return
|
return
|
||||||
|
|
||||||
self.uploadThread = QThread(self, self.flash_board, [
|
self.flash_board(self.uploadProgress, device, binary_uri,
|
||||||
self.signal, device, binary_uri], error=self.errorSignal)
|
error=self.errorSignal)
|
||||||
self.uploadThread.start()
|
|
||||||
|
|
||||||
def cache_download(self, progress, binary_uri):
|
def cache_download(self, progress, binary_uri):
|
||||||
"""Downloads and caches file with status reports via Qt Signals"""
|
"""Downloads and caches file with status reports via Qt Signals"""
|
||||||
@@ -226,19 +249,18 @@ class MainWindow(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
|
|
||||||
return cache_fname
|
return cache_fname
|
||||||
|
|
||||||
|
@QuickThread.wrap
|
||||||
def flash_board(self, progress, device, binary_uri, baudrate=460800):
|
def flash_board(self, progress, device, binary_uri, baudrate=460800):
|
||||||
if binary_uri.startswith(ALLOWED_PROTO):
|
if binary_uri.startswith(ALLOWED_PROTO):
|
||||||
binary_uri = self.cache_download(progress, binary_uri)
|
binary_uri = self.cache_download(progress, binary_uri)
|
||||||
|
|
||||||
print(binary_uri)
|
|
||||||
|
|
||||||
progress.emit(self.tr('Connecting...'), 0)
|
progress.emit(self.tr('Connecting...'), 0)
|
||||||
|
|
||||||
init_baud = min(ESPLoader.ESP_ROM_BAUD, baudrate)
|
init_baud = min(ESPLoader.ESP_ROM_BAUD, baudrate)
|
||||||
esp = ESPLoader.detect_chip(device, init_baud, 'default_reset', False)
|
esp = ESPLoader.detect_chip(device, init_baud, 'default_reset', False)
|
||||||
|
|
||||||
progress.emit(self.tr('Done. Chip type: %s') %
|
progress.emit(self.tr('Connected. Chip type: {chip_type}').format(
|
||||||
esp.get_chip_description(), 0)
|
chip_type=esp.get_chip_description()), 0)
|
||||||
esp = esp.run_stub()
|
esp = esp.run_stub()
|
||||||
esp.change_baud(baudrate)
|
esp.change_baud(baudrate)
|
||||||
|
|
||||||
@@ -255,7 +277,8 @@ class MainWindow(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
t = time.time()
|
t = time.time()
|
||||||
while len(image) > 0:
|
while len(image) > 0:
|
||||||
current_addr = address + seq * esp.FLASH_WRITE_SIZE
|
current_addr = address + seq * esp.FLASH_WRITE_SIZE
|
||||||
progress.emit(self.tr('Writing at 0x%08x...') % (current_addr,),
|
progress.emit(self.tr('Writing at 0x{address:08x}...').format(
|
||||||
|
address=current_addr),
|
||||||
100 * (seq + 1) // blocks)
|
100 * (seq + 1) // blocks)
|
||||||
|
|
||||||
block = image[0:esp.FLASH_WRITE_SIZE]
|
block = image[0:esp.FLASH_WRITE_SIZE]
|
||||||
@@ -265,14 +288,47 @@ class MainWindow(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
written += len(block)
|
written += len(block)
|
||||||
t = time.time() - t
|
t = time.time() - t
|
||||||
|
|
||||||
progress.emit(self.tr('Finished in %.2f seconds') % (t,), 100)
|
progress.emit(self.tr(
|
||||||
|
'Finished in {time:.2f} seconds. Sensor ID: {sensor_id}').format(
|
||||||
|
time=t, sensor_id=esp.chip_id()), 100)
|
||||||
|
|
||||||
@QtCore.Slot()
|
@QtCore.Slot()
|
||||||
def on_expertModeBox_clicked(self):
|
def on_expertModeBox_clicked(self):
|
||||||
self.expertForm.setVisible(self.expertModeBox.checkState())
|
self.expertForm.setVisible(self.expertModeBox.checkState())
|
||||||
|
#self.centralwidget.setFixedHeight(
|
||||||
|
# self.centralwidget.sizeHint().height())
|
||||||
|
#self.setFixedHeight(self.sizeHint().height())
|
||||||
|
|
||||||
|
# Zeroconf page
|
||||||
|
def discovery_start(self):
|
||||||
|
if self.zeroconf_discovery:
|
||||||
|
self.zeroconf_discovery.stop()
|
||||||
|
|
||||||
|
self.zeroconf_discovery = ZeroconfDiscoveryThread()
|
||||||
|
self.zeroconf_discovery.deviceDiscovered.connect(
|
||||||
|
self.on_zeroconf_discovered)
|
||||||
|
self.zeroconf_discovery.start()
|
||||||
|
|
||||||
|
|
||||||
|
def on_zeroconf_discovered(self, name, address, info):
|
||||||
|
"""Called on every zeroconf discovered device"""
|
||||||
|
if name.startswith('Feinstaubsensor'):
|
||||||
|
item = QtWidgets.QListWidgetItem('{}: {}'.format(address, name.split('.')[0]))
|
||||||
|
item.setData(ROLE_DEVICE, 'http://{}:{}'.format(address, info.port))
|
||||||
|
self.discoveryList.addItem(item)
|
||||||
|
|
||||||
|
@QtCore.Slot(QtWidgets.QListWidgetItem)
|
||||||
|
def on_discoveryList_itemDoubleClicked(self, index):
|
||||||
|
QtGui.QDesktopServices.openUrl(QtCore.QUrl(index.data(ROLE_DEVICE)))
|
||||||
|
|
||||||
|
@QtCore.Slot()
|
||||||
|
def on_discoveryRefreshButton_clicked(self):
|
||||||
|
self.discoveryList.clear()
|
||||||
|
self.discovery_start()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
app = QtWidgets.QApplication(sys.argv)
|
app = QtWidgets.QApplication(sys.argv)
|
||||||
window = MainWindow(app=app)
|
window = MainWindow(app=app)
|
||||||
window.show()
|
window.show()
|
||||||
|
|||||||
46
luftdaten-tool.spec
Normal file
46
luftdaten-tool.spec
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# -*- mode: python -*-
|
||||||
|
|
||||||
|
block_cipher = None
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
commit = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']).strip().decode('utf-8')
|
||||||
|
builddate = datetime.datetime.now().strftime('%Y%m%d')
|
||||||
|
|
||||||
|
with open('luftdatentool/_buildid.py', 'w') as fd:
|
||||||
|
fd.write('''# This file is autogenerated in luftdaten-tool.spec file
|
||||||
|
commit = "{commit}"
|
||||||
|
builddate = "{builddate}"'''.format(commit=commit, builddate=builddate))
|
||||||
|
|
||||||
|
a = Analysis(['luftdaten-tool.py'],
|
||||||
|
pathex=['.'],
|
||||||
|
binaries=[],
|
||||||
|
datas=[('assets/', './assets'), ('i18n/*.qm', './i18n')],
|
||||||
|
hiddenimports=['PyQt5.sip'],
|
||||||
|
hookspath=[],
|
||||||
|
runtime_hooks=[],
|
||||||
|
excludes=[],
|
||||||
|
win_no_prefer_redirects=False,
|
||||||
|
win_private_assemblies=False,
|
||||||
|
cipher=block_cipher)
|
||||||
|
pyz = PYZ(a.pure, a.zipped_data,
|
||||||
|
cipher=block_cipher)
|
||||||
|
exe = EXE(pyz,
|
||||||
|
a.scripts,
|
||||||
|
a.binaries,
|
||||||
|
a.zipfiles,
|
||||||
|
a.datas,
|
||||||
|
name='luftdaten-tool',
|
||||||
|
debug=False,
|
||||||
|
strip=False,
|
||||||
|
upx=True,
|
||||||
|
runtime_tmpdir=None,
|
||||||
|
console=False,
|
||||||
|
icon='assets/logo.ico')
|
||||||
|
|
||||||
|
# This is used on MacOS only
|
||||||
|
app = BUNDLE(exe,
|
||||||
|
name='Luftdaten.info Flashing Tool.app',
|
||||||
|
icon='assets/logo.icns',
|
||||||
|
bundle_identifier=None)
|
||||||
1
luftdatentool/__init__.py
Normal file
1
luftdatentool/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
__version__ = '0.2'
|
||||||
28
luftdatentool/consts.py
Normal file
28
luftdatentool/consts.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
from .qtvariant import QtCore
|
||||||
|
|
||||||
|
|
||||||
|
# Firmware update repository
|
||||||
|
UPDATE_REPOSITORY = 'https://www.madavi.de/sensor/update/data/'
|
||||||
|
|
||||||
|
# URI prefixes (protocol parts, essentially) to be downloaded using requests
|
||||||
|
ALLOWED_PROTO = ('http://', 'https://')
|
||||||
|
|
||||||
|
# vid/pid pairs of known NodeMCU/ESP8266 development boards
|
||||||
|
PREFERED_PORTS = [
|
||||||
|
# CH341
|
||||||
|
(0x1A86, 0x7523),
|
||||||
|
|
||||||
|
# CP2102
|
||||||
|
(0x10c4, 0xea60),
|
||||||
|
]
|
||||||
|
|
||||||
|
ROLE_DEVICE = QtCore.Qt.UserRole + 1
|
||||||
|
|
||||||
|
if sys.platform.startswith('darwin'):
|
||||||
|
DRIVERS_URL = 'http://www.wch.cn/downloads/CH341SER_MAC_ZIP.html'
|
||||||
|
elif sys.platform.startswith(('cygwin', 'win32')):
|
||||||
|
DRIVERS_URL = 'http://www.wch.cn/downloads/CH341SER_ZIP.html'
|
||||||
|
else:
|
||||||
|
DRIVERS_URL = None
|
||||||
8
luftdatentool/qtvariant.py
Normal file
8
luftdatentool/qtvariant.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
"""PyQt5 & PySide2 compatiblity layer stub. This will be updated when PySide2
|
||||||
|
gets mature enough"""
|
||||||
|
|
||||||
|
from PyQt5 import QtGui, QtCore, QtWidgets
|
||||||
|
|
||||||
|
# Replace nonsense prefixes
|
||||||
|
QtCore.Signal = QtCore.pyqtSignal
|
||||||
|
QtCore.Slot = QtCore.pyqtSlot
|
||||||
56
luftdatentool/utils.py
Normal file
56
luftdatentool/utils.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import requests
|
||||||
|
import re
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from .qtvariant import QtCore
|
||||||
|
|
||||||
|
|
||||||
|
file_index_re = re.compile(r'<a href="([^"]*)">([^<]*)</a>')
|
||||||
|
|
||||||
|
|
||||||
|
def indexof(path):
|
||||||
|
"""Returns list of filenames parsed off "Index of" page"""
|
||||||
|
resp = requests.get(path)
|
||||||
|
return [a for a, b in file_index_re.findall(resp.text) if a == b]
|
||||||
|
|
||||||
|
|
||||||
|
class QuickThread(QtCore.QThread):
|
||||||
|
error = QtCore.Signal([str])
|
||||||
|
|
||||||
|
"""Provides similar API to threading.Thread but with additional error
|
||||||
|
reporting based on Qt Signals"""
|
||||||
|
def __init__(self, parent=None, target=None, args=None, kwargs=None,
|
||||||
|
error=None):
|
||||||
|
super(QuickThread, self).__init__(parent)
|
||||||
|
self.target = target or self.target
|
||||||
|
self.args = args or []
|
||||||
|
self.kwargs = kwargs or {}
|
||||||
|
self.error = error or self.error
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
self.target(*self.args, **self.kwargs)
|
||||||
|
except Exception as exc:
|
||||||
|
if self.error:
|
||||||
|
self.error.emit(str(exc))
|
||||||
|
# raise here causes windows builds to just die. ¯\_(ツ)_/¯
|
||||||
|
logging.exception('Unhandled exception')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def wrap(cls, func):
|
||||||
|
"""Decorator that wraps function in a QThread. Calling resulting
|
||||||
|
function starts and creates QThread, with parent set to [self]"""
|
||||||
|
def wrapped(*args, **kwargs):
|
||||||
|
th = cls(parent=args[0], target=func, args=args, kwargs=kwargs,
|
||||||
|
error=kwargs.pop('error', None))
|
||||||
|
func._th = th
|
||||||
|
th.start()
|
||||||
|
|
||||||
|
return th
|
||||||
|
|
||||||
|
wrapped.running = lambda: (hasattr(func, '_th') and
|
||||||
|
func._th.isRunning())
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
def target(self):
|
||||||
|
pass
|
||||||
60
luftdatentool/workers.py
Normal file
60
luftdatentool/workers.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import time
|
||||||
|
import socket
|
||||||
|
|
||||||
|
import serial
|
||||||
|
import serial.tools.list_ports
|
||||||
|
import zeroconf
|
||||||
|
|
||||||
|
from .qtvariant import QtCore
|
||||||
|
from .utils import indexof, QuickThread
|
||||||
|
from .consts import UPDATE_REPOSITORY
|
||||||
|
|
||||||
|
|
||||||
|
class PortDetectThread(QuickThread):
|
||||||
|
interval = 1.0
|
||||||
|
portsUpdate = QtCore.Signal([list])
|
||||||
|
|
||||||
|
def target(self):
|
||||||
|
"""Checks list of available ports and emits signal when necessary"""
|
||||||
|
|
||||||
|
ports = None
|
||||||
|
while True:
|
||||||
|
new_ports = serial.tools.list_ports.comports()
|
||||||
|
|
||||||
|
if ports is None or [p.name for p in ports] != [p.name for p in new_ports]:
|
||||||
|
self.portsUpdate.emit(new_ports)
|
||||||
|
|
||||||
|
time.sleep(self.interval)
|
||||||
|
|
||||||
|
ports = new_ports
|
||||||
|
|
||||||
|
|
||||||
|
class FirmwareListThread(QuickThread):
|
||||||
|
listLoaded = QtCore.Signal([list])
|
||||||
|
|
||||||
|
def target(self):
|
||||||
|
"""Downloads list of available firmware updates in separate thread."""
|
||||||
|
self.listLoaded.emit(list(indexof(UPDATE_REPOSITORY)))
|
||||||
|
|
||||||
|
|
||||||
|
class ZeroconfDiscoveryThread(QuickThread):
|
||||||
|
deviceDiscovered = QtCore.Signal(str, str, object)
|
||||||
|
browser = None
|
||||||
|
|
||||||
|
def target(self):
|
||||||
|
"""This thread scans for Bonjour/mDNS devices and emits
|
||||||
|
deviceDiscovered signal with its name, address and info object"""
|
||||||
|
self.zc = zeroconf.Zeroconf()
|
||||||
|
self.browser = zeroconf.ServiceBrowser(self.zc, "_http._tcp.local.",
|
||||||
|
handlers=[self.on_state_change])
|
||||||
|
while True:
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
def on_state_change(self, zeroconf, service_type, name, state_change):
|
||||||
|
info = zeroconf.get_service_info(service_type, name)
|
||||||
|
if info:
|
||||||
|
self.deviceDiscovered.emit(name, socket.inet_ntoa(info.address), info)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
if self.browser:
|
||||||
|
self.browser.cancel()
|
||||||
@@ -1,11 +1,18 @@
|
|||||||
|
altgraph==0.16.1
|
||||||
certifi==2018.8.24
|
certifi==2018.8.24
|
||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
ecdsa==0.13
|
ecdsa==0.13
|
||||||
esptool==2.5.0
|
esptool==2.5.0
|
||||||
|
future==0.16.0
|
||||||
idna==2.7
|
idna==2.7
|
||||||
|
macholib==1.11
|
||||||
|
netifaces==0.10.7
|
||||||
|
pefile==2018.8.8
|
||||||
pyaes==1.6.1
|
pyaes==1.6.1
|
||||||
|
https://github.com/pyinstaller/pyinstaller/archive/bbf964c6b89ca33823031fa7ed277c0269192b3e.zip#egg=PyInstaller
|
||||||
PyQt5==5.11.2
|
PyQt5==5.11.2
|
||||||
PyQt5-sip==4.19.12
|
PyQt5-sip==4.19.12
|
||||||
pyserial==3.4
|
pyserial==3.4
|
||||||
requests==2.19.1
|
requests==2.19.1
|
||||||
urllib3==1.23
|
urllib3==1.23
|
||||||
|
zeroconf==0.20.0
|
||||||
|
|||||||
Reference in New Issue
Block a user