Initial refactor, zeroconf tests
This commit is contained in:
parent
af335bac94
commit
1042835685
2
Makefile
2
Makefile
@ -1,6 +1,6 @@
|
|||||||
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)
|
||||||
|
@ -2,121 +2,33 @@
|
|||||||
|
|
||||||
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 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
|
|
||||||
|
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
|
||||||
|
|
||||||
# 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 getattr(sys, 'frozen', False):
|
if getattr(sys, 'frozen', False):
|
||||||
RESOURCES_PATH = sys._MEIPASS
|
RESOURCES_PATH = sys._MEIPASS
|
||||||
else:
|
else:
|
||||||
RESOURCES_PATH = os.path.dirname(os.path.realpath(__file__))
|
RESOURCES_PATH = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|
||||||
# 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 QuickThread(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(QuickThread, 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 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
|
|
||||||
|
|
||||||
|
|
||||||
class PortDetectThread(QtCore.QThread):
|
|
||||||
interval = 1.0
|
|
||||||
portsUpdate = QtCore.Signal([list])
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
"""Checks list of available ports and emits signal when necessary"""
|
|
||||||
|
|
||||||
ports = []
|
|
||||||
while True:
|
|
||||||
new_ports = serial.tools.list_ports.comports()
|
|
||||||
|
|
||||||
if [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(QtCore.QThread):
|
|
||||||
onFirmware = QtCore.Signal([list])
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
"""Downloads list of available firmware updates in separate thread."""
|
|
||||||
self.onFirmware.emit(list(indexof(UPDATE_REPOSITORY)))
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@ -136,17 +48,20 @@ class MainWindow(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
self.i18n_init(QtCore.QLocale.system())
|
self.i18n_init(QtCore.QLocale.system())
|
||||||
|
|
||||||
self.statusbar.showMessage(self.tr("Loading firmware list..."))
|
self.statusbar.showMessage(self.tr("Loading firmware list..."))
|
||||||
|
|
||||||
self.firmware_list = FirmwareListThread()
|
self.firmware_list = FirmwareListThread()
|
||||||
self.firmware_list.onFirmware.connect(self.populate_versions)
|
self.firmware_list.listLoaded.connect(self.populate_versions)
|
||||||
|
self.firmware_list.error.connect(self.on_work_error)
|
||||||
self.firmware_list.start()
|
self.firmware_list.start()
|
||||||
|
|
||||||
self.port_detect = PortDetectThread()
|
self.port_detect = PortDetectThread()
|
||||||
self.port_detect.portsUpdate.connect(self.populate_boards)
|
self.port_detect.portsUpdate.connect(self.populate_boards)
|
||||||
|
self.port_detect.error.connect(self.on_work_error)
|
||||||
self.port_detect.start()
|
self.port_detect.start()
|
||||||
|
|
||||||
self.on_expertModeBox_clicked()
|
self.on_expertModeBox_clicked()
|
||||||
|
|
||||||
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()
|
||||||
@ -257,7 +172,7 @@ class MainWindow(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
self.statusbar.showMessage(self.tr("Work in progess..."))
|
self.statusbar.showMessage(self.tr("Work in progess..."))
|
||||||
return
|
return
|
||||||
|
|
||||||
self.flash_board(self.signal, device, binary_uri,
|
self.flash_board(self.uploadProgress, device, binary_uri,
|
||||||
error=self.errorSignal)
|
error=self.errorSignal)
|
||||||
|
|
||||||
def cache_download(self, progress, binary_uri):
|
def cache_download(self, progress, binary_uri):
|
||||||
@ -291,8 +206,6 @@ class MainWindow(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
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)
|
||||||
|
0
luftdatentool/__init__.py
Normal file
0
luftdatentool/__init__.py
Normal file
18
luftdatentool/consts.py
Normal file
18
luftdatentool/consts.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
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
|
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
|
53
luftdatentool/workers.py
Normal file
53
luftdatentool/workers.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
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 = []
|
||||||
|
while True:
|
||||||
|
new_ports = serial.tools.list_ports.comports()
|
||||||
|
|
||||||
|
if [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)
|
||||||
|
|
||||||
|
def target(self):
|
||||||
|
zc = zeroconf.Zeroconf()
|
||||||
|
browser = zeroconf.ServiceBrowser(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)
|
@ -6,6 +6,7 @@ esptool==2.5.0
|
|||||||
future==0.16.0
|
future==0.16.0
|
||||||
idna==2.7
|
idna==2.7
|
||||||
macholib==1.11
|
macholib==1.11
|
||||||
|
netifaces==0.10.7
|
||||||
pefile==2018.8.8
|
pefile==2018.8.8
|
||||||
pyaes==1.6.1
|
pyaes==1.6.1
|
||||||
https://github.com/pyinstaller/pyinstaller/archive/bbf964c6b89ca33823031fa7ed277c0269192b3e.zip#egg=PyInstaller
|
https://github.com/pyinstaller/pyinstaller/archive/bbf964c6b89ca33823031fa7ed277c0269192b3e.zip#egg=PyInstaller
|
||||||
@ -14,3 +15,4 @@ 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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user