2020-12-02 02:47:11 -05:00
|
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
|
|
2020-12-02 10:29:28 -05:00
|
|
|
import datetime
|
2020-12-02 11:11:29 -05:00
|
|
|
import struct
|
|
|
|
|
import time
|
|
|
|
|
import threading
|
2020-12-02 10:29:28 -05:00
|
|
|
|
2020-12-02 11:11:29 -05:00
|
|
|
from PyQt5.QtCore import pyqtSignal
|
2020-12-02 10:29:28 -05:00
|
|
|
from PyQt5.QtGui import QFontDatabase
|
|
|
|
|
from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QLineEdit, QToolButton, QPlainTextEdit, QProgressBar,\
|
|
|
|
|
QFileDialog, QDialog
|
2020-12-02 02:47:11 -05:00
|
|
|
|
2020-12-02 11:11:29 -05:00
|
|
|
from util import tr, chunks
|
2020-12-02 10:10:59 -05:00
|
|
|
from vial_device import VialBootloader
|
2020-12-02 02:47:11 -05:00
|
|
|
|
|
|
|
|
|
2020-12-02 11:11:29 -05:00
|
|
|
def send_retries(dev, data, retries=20):
|
|
|
|
|
""" Sends usb packet up to 'retries' times, returns True if success, False if failed """
|
|
|
|
|
|
|
|
|
|
for x in range(retries):
|
|
|
|
|
ret = dev.send(data)
|
|
|
|
|
if ret == len(data) + 1:
|
|
|
|
|
return True
|
|
|
|
|
elif ret < 0:
|
|
|
|
|
time.sleep(0.1)
|
|
|
|
|
else:
|
|
|
|
|
return False
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CHUNK = 64
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def cmd_flash(device, firmware, log):
|
|
|
|
|
# Check bootloader is correct version
|
|
|
|
|
device.send(b"VC\x00")
|
|
|
|
|
data = device.recv(8)
|
|
|
|
|
log("* Bootloader version: {}".format(data[0]))
|
|
|
|
|
if data[0] != 0:
|
|
|
|
|
log("Error: Unsupported bootloader version")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# TODO: Check vial ID against firmware package
|
|
|
|
|
device.send(b"VC\x01")
|
|
|
|
|
data = device.recv(8)
|
|
|
|
|
log("* Vial ID: {}".format(data.hex()))
|
|
|
|
|
|
|
|
|
|
# Flash
|
|
|
|
|
firmware_size = len(firmware)
|
|
|
|
|
if firmware_size % CHUNK != 0:
|
|
|
|
|
firmware_size += CHUNK - firmware_size % CHUNK
|
|
|
|
|
log(device.send(b"VC\x02" + struct.pack("<H", firmware_size // CHUNK)))
|
|
|
|
|
for part in chunks(firmware, CHUNK):
|
|
|
|
|
if len(part) < CHUNK:
|
|
|
|
|
part += b"\x00" * (CHUNK - len(part))
|
|
|
|
|
if not send_retries(device, part):
|
|
|
|
|
log("Error while sending data, firmware is corrupted.")
|
|
|
|
|
return
|
|
|
|
|
log(datetime.datetime.now())
|
|
|
|
|
|
|
|
|
|
# Reboot
|
|
|
|
|
log("Rebooting...")
|
|
|
|
|
device.send(b"VC\x03")
|
|
|
|
|
|
|
|
|
|
|
2020-12-02 02:47:11 -05:00
|
|
|
class FirmwareFlasher(QVBoxLayout):
|
2020-12-02 11:11:29 -05:00
|
|
|
log_signal = pyqtSignal(object)
|
2020-12-02 02:47:11 -05:00
|
|
|
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
2020-12-02 11:11:29 -05:00
|
|
|
self.log_signal.connect(self._on_log)
|
|
|
|
|
|
2020-12-02 10:29:28 -05:00
|
|
|
self.selected_firmware_path = ""
|
|
|
|
|
|
2020-12-02 02:47:11 -05:00
|
|
|
file_selector = QHBoxLayout()
|
2020-12-02 10:29:28 -05:00
|
|
|
self.txt_file_selector = QLineEdit()
|
|
|
|
|
self.txt_file_selector.setReadOnly(True)
|
|
|
|
|
file_selector.addWidget(self.txt_file_selector)
|
2020-12-02 02:47:11 -05:00
|
|
|
btn_select_file = QToolButton()
|
|
|
|
|
btn_select_file.setText(tr("Flasher", "Select file..."))
|
2020-12-02 10:29:28 -05:00
|
|
|
btn_select_file.clicked.connect(self.on_click_select_file)
|
2020-12-02 02:47:11 -05:00
|
|
|
file_selector.addWidget(btn_select_file)
|
|
|
|
|
self.addLayout(file_selector)
|
2020-12-02 10:29:28 -05:00
|
|
|
self.txt_logger = QPlainTextEdit()
|
|
|
|
|
self.txt_logger.setReadOnly(True)
|
|
|
|
|
self.txt_logger.setFont(QFontDatabase.systemFont(QFontDatabase.FixedFont))
|
|
|
|
|
self.addWidget(self.txt_logger)
|
2020-12-02 02:47:11 -05:00
|
|
|
progress_flash = QHBoxLayout()
|
|
|
|
|
progress_flash.addWidget(QProgressBar())
|
|
|
|
|
btn_flash = QToolButton()
|
|
|
|
|
btn_flash.setText(tr("Flasher", "Flash"))
|
2020-12-02 10:29:28 -05:00
|
|
|
btn_flash.clicked.connect(self.on_click_flash)
|
2020-12-02 02:47:11 -05:00
|
|
|
progress_flash.addWidget(btn_flash)
|
|
|
|
|
self.addLayout(progress_flash)
|
2020-12-02 10:10:59 -05:00
|
|
|
|
|
|
|
|
self.device = None
|
|
|
|
|
|
|
|
|
|
def rebuild(self, device):
|
|
|
|
|
self.device = device
|
2020-12-02 10:29:28 -05:00
|
|
|
self.txt_logger.clear()
|
|
|
|
|
if isinstance(self.device, VialBootloader):
|
|
|
|
|
self.log("Valid Vial Bootloader device at {}".format(self.device.desc["path"].decode("utf-8")))
|
2020-12-02 10:10:59 -05:00
|
|
|
|
|
|
|
|
def valid(self):
|
2020-12-02 10:29:28 -05:00
|
|
|
# TODO: it is also valid to flash a VialKeyboard which supports optional "vibl-integration" feature
|
2020-12-02 10:10:59 -05:00
|
|
|
return isinstance(self.device, VialBootloader)
|
2020-12-02 10:29:28 -05:00
|
|
|
|
|
|
|
|
def on_click_select_file(self):
|
|
|
|
|
dialog = QFileDialog()
|
|
|
|
|
# TODO: this should be .vfw for Vial Firmware
|
|
|
|
|
dialog.setDefaultSuffix("bin")
|
|
|
|
|
dialog.setAcceptMode(QFileDialog.AcceptOpen)
|
|
|
|
|
dialog.setNameFilters(["Vial Firmware (*.bin)"])
|
|
|
|
|
if dialog.exec_() == QDialog.Accepted:
|
|
|
|
|
self.selected_firmware_path = dialog.selectedFiles()[0]
|
|
|
|
|
self.txt_file_selector.setText(self.selected_firmware_path)
|
|
|
|
|
self.log("Firmware update package: {}".format(self.selected_firmware_path))
|
|
|
|
|
|
|
|
|
|
def on_click_flash(self):
|
2020-12-02 11:11:29 -05:00
|
|
|
if not self.selected_firmware_path:
|
|
|
|
|
self.log("Error: Please select a firmware update package")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
with open(self.selected_firmware_path, "rb") as inf:
|
|
|
|
|
firmware = inf.read()
|
|
|
|
|
|
|
|
|
|
if len(firmware) > 10 * 1024 * 1024:
|
|
|
|
|
self.log("Error: Firmware is too large. Check you've selected the correct file")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
self.log("Preparing to flash...")
|
|
|
|
|
threading.Thread(target=lambda: cmd_flash(self.device, firmware, self.on_log)).start()
|
|
|
|
|
|
|
|
|
|
def on_log(self, line):
|
|
|
|
|
self.log_signal.emit(line)
|
|
|
|
|
|
|
|
|
|
def _on_log(self, line):
|
|
|
|
|
self.log(line)
|
2020-12-02 10:29:28 -05:00
|
|
|
|
|
|
|
|
def log(self, line):
|
|
|
|
|
self.txt_logger.appendPlainText("[{}] {}".format(datetime.datetime.now(), line))
|