Protobuf rpc (#34)

* Setup the nanopb environment

* Recursive checkout

* Include .pri file

* Add bytesWritten information to AbstractSerialOperation

* Add Start RPC Session operation

* Better ScreenStream log messages

* Incomplete screen streaming protobuf implementation - saving work

* Working sreen streaming over protobuf [no input]

* Common class for all Protobuf messages

* Rename ProtobufMessage to MainProtobufMessage

* Finished NanoPB wrapper

* Add new protobuf headers

* Implement DeviceInfo through Protobuf

* File structure reorganisation

* Even better protobuf implementation

* Migrate StorageStat operation to protobuf

* Add Gui input request

* Bring back input events

* Reusable response messages

* Update protobuf message definitions

* Migrate Storage List to protobuf

* Add missing storage messages

* Add missing system messages

* Disable screen streaming for the time being

* Migrate Storage Write to protobuf

* Migrate Storage Read command to protobuf

* Working Backup operation

* Fix date conversion

* Migrate Storage Remove to protobuf

* Migrate Storage mkdir command to protobuf

* Error message tweaks

* Fix StorageWrite operation

* Working Storage Write operation via protobuf

* Code cleanup

* Add comment

* Add timeout to AbstractSerialOperation

* Migrate FactoryReset operation to protobuf

* Migrate SystemReboot operation to protobuf

* Rename some files

* Unified serial (#32)

* Use only one pre-initialised instance of QSerialPort - draft

* Correct error string descriptions

* Rewind the output file for write operation

* Simplify serial port management

* Simplify DeviceInfoHelper

* Keep the serial port instance in a shared pointer

* Add Start and stop screen streaming operations

* Integrate Gui operatons into CommandInterface

* Implement a shared instance of CommandInterface

* Bring back ScreenStreaming

* Disable screen streaming - it doesn't work

* Bug fixes

* Remove unnecessary output

* Use DeviceState in screen streamer

* Arch refactoring (#33)

* Add periodic update checking

* Add error message for update registry

* Application backend is now a QObject

* Rename some classes

* Use dynamically allocated QObjects everywhere for consistency

* Add useful function stubs

* Move MetaType registration into separate static method

* Revert to static allocation in Application class because of QML errors

* Simplify MainWindow code

* Implement working update procedure w/screen streaming

* Fix windows build(again!)

* Remove extra include

* Improve serial port handling

* Handle the absence of SD card properly

* Close and delete serial port instances correctly

* Rename slots

* Remove unused signal

* Cleanup screen streamer code

* Add started and stopped signals for sreen streamer

* Almost get rid of FirmwareUpdater class

* Bring back all operations

* Remove deviceRegistry context property

* Add debriefing screen

* Remove mentions of firmwareUpdater from QML code

* Remove FirmwareUpdater class

* Increase shadow opacity

* Bring back full screen streaming

* Remove offline devices after an operaton

* Implement self update dialog

* Remove unneeded condition
This commit is contained in:
Georgii Surkov 2021-12-22 21:20:41 +03:00 committed by GitHub
parent e3a70cfcc4
commit ae7ee417fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
134 changed files with 4895 additions and 2103 deletions

3
.gitmodules vendored
View File

@ -1,3 +1,6 @@
[submodule "driver-tool/libwdi"]
path = driver-tool/libwdi
url = https://github.com/pbatard/libwdi.git
[submodule "3rdparty/nanopb"]
path = 3rdparty/nanopb
url = https://github.com/nanopb/nanopb.git

17
3rdparty/3rdparty.pro vendored Normal file
View File

@ -0,0 +1,17 @@
CONFIG -= qt
TEMPLATE = lib
CONFIG += staticlib c++11
include(../qflipper_common.pri)
SOURCES += \
nanopb/pb_common.c \
nanopb/pb_decode.c \
nanopb/pb_encode.c
HEADERS += \
nanopb/pb.h \
nanopb/pb_common.h \
nanopb/pb_decode.h \
nanopb/pb_encode.h

1
3rdparty/nanopb vendored Submodule

@ -0,0 +1 @@
Subproject commit 13666952914f3cf43a70c6b9738a7dc0dd06a6dc

View File

@ -10,8 +10,8 @@
#include <QLoggingCategory>
#include <QtQuickControls2/QQuickStyle>
#include "qflipperbackend.h"
#include "updateregistry.h"
#include "deviceregistry.h"
#include "screencanvas.h"
#include "preferences.h"
#include "logger.h"
@ -44,7 +44,7 @@ const QString Application::commitNumber()
return APP_COMMIT;
}
AppUpdater *Application::updater()
ApplicationUpdater *Application::updater()
{
return &m_updater;
}
@ -65,9 +65,8 @@ void Application::initContextProperties()
{
//TODO: Replace context properties with QML singletons
m_engine.rootContext()->setContextProperty("app", this);
m_engine.rootContext()->setContextProperty("deviceRegistry", &m_backend.deviceRegistry);
m_engine.rootContext()->setContextProperty("firmwareUpdates", &m_backend.firmwareUpdates);
m_engine.rootContext()->setContextProperty("applicationUpdates", &m_backend.applicationUpdates);
m_engine.rootContext()->setContextProperty("firmwareUpdates", m_backend.firmwareUpdates());
m_engine.rootContext()->setContextProperty("applicationUpdates",m_backend.applicationUpdates());
}
void Application::initTranslations()
@ -86,10 +85,12 @@ void Application::initTranslations()
void Application::initQmlTypes()
{
qmlRegisterType<ScreenCanvas>("QFlipper", 1, 0, "ScreenCanvas");
qmlRegisterType<AppUpdater>("QFlipper", 1, 0, "AppUpdater");
qmlRegisterType<ApplicationUpdater>("QFlipper", 1, 0, "AppUpdater");
qmlRegisterSingletonInstance("QFlipper", 1, 0, "Logger", globalLogger);
qmlRegisterSingletonInstance("QFlipper", 1, 0, "Preferences", globalPrefs);
qmlRegisterSingletonInstance("QFlipper", 1, 0, "Backend", &m_backend);
qmlRegisterSingletonInstance("QFlipper", 1, 0, "Application", this);
}
void Application::initImports()

View File

@ -3,9 +3,8 @@
#include <QApplication>
#include <QQmlApplicationEngine>
#include "appupdater.h"
#include "flipperupdates.h"
#include "qflipperbackend.h"
#include "applicationupdater.h"
#include "applicationbackend.h"
class Application : public QApplication
{
@ -13,15 +12,14 @@ class Application : public QApplication
Q_PROPERTY(QString name READ applicationName NOTIFY applicationNameChanged)
Q_PROPERTY(QString version READ applicationVersion NOTIFY applicationVersionChanged)
Q_PROPERTY(QString commit READ commitNumber CONSTANT)
Q_PROPERTY(AppUpdater* updater READ updater CONSTANT)
Q_PROPERTY(ApplicationUpdater* updater READ updater CONSTANT)
public:
Application(int &argc, char **argv);
~Application();
static const QString commitNumber();
AppUpdater *updater();
ApplicationUpdater *updater();
private:
void initLogger();
@ -33,8 +31,8 @@ private:
void initFonts();
void initGUI();
AppUpdater m_updater;
QFlipperBackend m_backend;
ApplicationUpdater m_updater;
ApplicationBackend m_backend;
QQmlApplicationEngine m_engine;
};

View File

@ -9,7 +9,7 @@ CONFIG += c++11
SOURCES += \
application.cpp \
appupdater.cpp \
applicationupdater.cpp \
main.cpp \
screencanvas.cpp
@ -30,17 +30,20 @@ QML_IMPORT_PATH = $$PWD/imports
unix|win32 {
LIBS += \
-L$$OUT_PWD/../backend/ -lbackend \
-L$$OUT_PWD/../3rdparty/ -l3rdparty \
-L$$OUT_PWD/../dfu/ -ldfu
}
win32:!win32-g++ {
PRE_TARGETDEPS += \
$$OUT_PWD/../backend/backend.lib \
$$OUT_PWD/../3rdparty/3rdparty.lib \
$$OUT_PWD/../dfu/dfu.lib
} else:unix|win32-g++ {
PRE_TARGETDEPS += \
$$OUT_PWD/../backend/libbackend.a \
$$OUT_PWD/../3rdparty/lib3rdparty.a \
$$OUT_PWD/../dfu/libdfu.a
}
@ -68,11 +71,12 @@ INCLUDEPATH += \
DEPENDPATH += \
$$PWD/../dfu \
$$PWD/../backend
$$PWD/../backend \
$$PWD/../3rdparty \
HEADERS += \
application.h \
appupdater.h \
applicationupdater.h \
screencanvas.h
DISTFILES +=

View File

@ -1,4 +1,4 @@
#include "appupdater.h"
#include "applicationupdater.h"
#include <QDir>
#include <QFile>
@ -19,23 +19,23 @@ static const QString chopRcSuffix(const QString &str) {
return suffixIdx < 0 ? str : str.chopped(str.length() - suffixIdx);
}
AppUpdater::AppUpdater(QObject *parent):
ApplicationUpdater::ApplicationUpdater(QObject *parent):
QObject(parent),
m_state(State::Idle),
m_progress(0)
{}
AppUpdater::State AppUpdater::state() const
ApplicationUpdater::State ApplicationUpdater::state() const
{
return m_state;
}
double AppUpdater::progress() const
double ApplicationUpdater::progress() const
{
return m_progress;
}
bool AppUpdater::canUpdate(const Flipper::Updates::VersionInfo &versionInfo)
bool ApplicationUpdater::canUpdate(const Flipper::Updates::VersionInfo &versionInfo)
{
const auto appDate = QDateTime::fromSecsSinceEpoch(APP_TIMESTAMP).date();
const auto appVersion = QStringLiteral(APP_VERSION);
@ -61,7 +61,7 @@ bool AppUpdater::canUpdate(const Flipper::Updates::VersionInfo &versionInfo)
}
}
void AppUpdater::installUpdate(const Flipper::Updates::VersionInfo &versionInfo)
void ApplicationUpdater::installUpdate(const Flipper::Updates::VersionInfo &versionInfo)
{
#ifdef Q_OS_WINDOWS
const auto fileInfo = versionInfo.fileInfo(QStringLiteral("installer"), QStringLiteral("windows/amd64"));
@ -138,7 +138,7 @@ void AppUpdater::installUpdate(const Flipper::Updates::VersionInfo &versionInfo)
}
});
connect(fetcher, &RemoteFileFetcher::progressChanged, this, &AppUpdater::setProgress);
connect(fetcher, &RemoteFileFetcher::progressChanged, this, &ApplicationUpdater::setProgress);
if(!fetcher->fetch(fileInfo, file)) {
qCWarning(CATEGORY_SELFUPDATES) << "Failed to start downloading the update package.";
@ -153,7 +153,7 @@ void AppUpdater::installUpdate(const Flipper::Updates::VersionInfo &versionInfo)
}
}
void AppUpdater::setState(State state)
void ApplicationUpdater::setState(State state)
{
if(m_state == state) {
return;
@ -163,7 +163,7 @@ void AppUpdater::setState(State state)
emit stateChanged();
}
void AppUpdater::setProgress(double progress)
void ApplicationUpdater::setProgress(double progress)
{
if(qFuzzyCompare(m_progress, progress)) {
return;
@ -173,7 +173,7 @@ void AppUpdater::setProgress(double progress)
emit progressChanged();
}
bool AppUpdater::performUpdate(const QString &path)
bool ApplicationUpdater::performUpdate(const QString &path)
{
const auto exitApplication = []() {
qCInfo(CATEGORY_SELFUPDATES) << "Update started, exiting the application...";

View File

@ -4,7 +4,7 @@
#include "flipperupdates.h"
class AppUpdater : public QObject
class ApplicationUpdater : public QObject
{
Q_OBJECT
Q_PROPERTY(double progress READ progress NOTIFY progressChanged)
@ -20,7 +20,7 @@ public:
Q_ENUM(State)
AppUpdater(QObject *parent = nullptr);
ApplicationUpdater(QObject *parent = nullptr);
State state() const;
double progress() const;

View File

@ -1,11 +1,13 @@
import QtQuick 2.15
import QFlipper 1.0
Item {
id: overlay
property Rectangle backgroundRect
readonly property var device: deviceRegistry.currentDevice
readonly property var device: Backend.currentDevice
readonly property var deviceState: device ? device.state : undefined
readonly property var deviceInfo: deviceState ? deviceState.info : undefined

View File

@ -15,7 +15,7 @@ ColumnLayout {
property alias reinstallAction: reinstallAction
property alias selfUpdateAction: selfUpdateAction
readonly property var device: deviceRegistry.currentDevice
readonly property var device: Backend.currentDevice
readonly property var deviceState: device ? device.state : undefined
TransparentLabel {
@ -146,13 +146,12 @@ ColumnLayout {
Action {
id: reinstallAction
text: qsTr("Reinstall")
enabled: firmwareUpdates.isReady && !!deviceState && !deviceState.isRecoveryMode &&
!(device.updater.canUpdate(firmwareUpdates.latestVersion) || device.updater.canInstall())
enabled: Backend.updateStatus === Backend.NoUpdates
}
Action {
id: selfUpdateAction
text: qsTr("Update qFlipper")
enabled: applicationUpdates.isReady && app.updater.canUpdate(applicationUpdates.latestVersion)
text: qsTr("Check app updates")
enabled: false//applicationUpdates.isReady && app.updater.canUpdate(applicationUpdates.latestVersion)
}
}

View File

@ -1,13 +1,14 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QFlipper 1.0
import Theme 1.0
RowLayout {
id: control
spacing: 30
readonly property var device: deviceRegistry.currentDevice
readonly property var device: Backend.currentDevice
readonly property var deviceState: device ? device.state : undefined
readonly property var deviceInfo: deviceState ? deviceState.info : undefined
readonly property bool extraFields: deviceState ? !deviceState.isRecoveryMode : false

View File

@ -9,7 +9,7 @@ Image {
signal screenStreamRequested
readonly property var device: deviceRegistry.currentDevice
readonly property var device: Backend.currentDevice
readonly property var deviceState: device ? device.state : undefined
visible: opacity > 0

View File

@ -0,0 +1,72 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QFlipper 1.0
import Theme 1.0
AbstractOverlay {
id: overlay
TextLabel {
id: updateLabel
capitalized: false
anchors.horizontalCenter: parent.horizontalCenter
y: 24
color: Backend.state === Backend.ErrorOccured ? Theme.color.lightred3 : Theme.color.lightorange2
font.family: "Born2bSportyV2"
font.pixelSize: 48
text: Backend.state === Backend.ErrorOccured ? qsTr("Epic fail!"): qsTr("Success!")
}
TextLabel {
anchors.top: parent.top
anchors.left: parent.left
anchors.topMargin: 45
anchors.leftMargin: 35
text: "Work in progress"
}
TextLabel {
anchors.top: parent.top
anchors.right: parent.right
anchors.topMargin: 45
anchors.rightMargin: 35
text: "Work in progress"
}
TextLabel {
id: messageLabel
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 105
text: Backend.state === Backend.Finished ? qsTr("You're good to go!") : deviceState ? deviceState.errorString : qsTr("Cannot connect to device")
}
Button {
id: backButton
action: backAction
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 35
icon.width: 24
icon.height: 24
icon.source: "qrc:/assets/gfx/symbolic/arrow-back.svg"
}
Action {
id: backAction
text: qsTr("Back")
onTriggered: Backend.finalizeOperation()
}
}

View File

@ -128,8 +128,16 @@ AbstractOverlay {
x: Math.round(centerX - width / 2)
y: 265
accent: !(firmwareUpdates.isReady && deviceState) ? accent : deviceState.isRecoveryMode ? UpdateButton.Blue :
device.updater.canUpdate(firmwareUpdates.latestVersion) ? UpdateButton.Green : UpdateButton.Default
accent: {
switch(Backend.updateStatus) {
case Backend.CanRepair:
return UpdateButton.Blue;
case Backend.CanUpdate:
return UpdateButton.Green;
default:
return UpdateButton.Default;
}
}
}
LinkButton {
@ -168,12 +176,22 @@ AbstractOverlay {
Action {
id: updateButtonAction
enabled: firmwareUpdates.isReady && !!deviceState &&
(deviceState.isRecoveryMode || device.updater.canUpdate(firmwareUpdates.latestVersion) || device.updater.canInstall())
text: !(firmwareUpdates.isReady && deviceState) ? qsTr("No data") : deviceState.isRecoveryMode ? qsTr("Repair") :
device.updater.canUpdate(firmwareUpdates.latestVersion) ? qsTr("Update") :
device.updater.canInstall() ? qsTr("Install") : qsTr("No updates")
enabled: (Backend.updateStatus !== Backend.Unknown) &&
(Backend.updateStatus !== Backend.NoUpdates)
text: {
switch(Backend.updateStatus) {
case Backend.CanRepair:
return qsTr("Repair");
case Backend.CanUpdate:
return qsTr("Update");
case Backend.CanInstall:
return qsTr("Install");
case Backend.NoUpdates:
return qsTr("No updates");
case Backend.Unknown:
return qsTr("No data");
}
}
onTriggered: updateButtonFunc()
}
@ -213,23 +231,13 @@ AbstractOverlay {
const channelName = Preferences.updateChannel;
const latestVersion = firmwareUpdates.latestVersion;
let messageObj, actionFunc;
if(deviceState.isRecoveryMode) {
messageObj = {
const messageObj = deviceState.isRecoveryMode ? {
title : qsTr("Repair Device?"),
customText: qsTr("Repair"),
message : qsTr("Firmware <font color=\"%1\">%2</font><br/>will be installed")
.arg(releaseButton.linkColor)
.arg(releaseButton.text)
};
actionFunc = function() {
device.updater.fullRepair(latestVersion);
}
} else {
messageObj = {
} : {
title : qsTr("Update firmware?"),
customText: qsTr("Update"),
message: qsTr("New firmware <font color=\"%1\">%2</font><br/>will be installed")
@ -237,12 +245,7 @@ AbstractOverlay {
.arg(releaseButton.text),
};
actionFunc = function() {
device.updater.fullUpdate(latestVersion);
}
}
confirmationDialog.openWithMessage(actionFunc, messageObj);
confirmationDialog.openWithMessage(Backend.mainAction, messageObj);
}
function installFromFile() {
@ -257,7 +260,7 @@ AbstractOverlay {
};
const actionFunc = function() {
device.updater.localFirmwareInstall(fileDialog.fileUrl);
Backend.installFirmware(fileDialog.fileUrl);
}
confirmationDialog.openWithMessage(actionFunc, messageObj);
@ -278,7 +281,7 @@ AbstractOverlay {
};
const actionFunc = function() {
device.updater.backupInternalStorage(fileDialog.fileUrl);
Backend.createBackup(fileDialog.fileUrl);
}
confirmationDialog.openWithMessage(actionFunc, messageObj);
@ -299,7 +302,7 @@ AbstractOverlay {
};
const actionFunc = function() {
device.updater.restoreInternalStorage(fileDialog.fileUrl);
Backend.restoreBackup(fileDialog.fileUrl);
}
confirmationDialog.openWithMessage(actionFunc, messageObj);
@ -316,11 +319,7 @@ AbstractOverlay {
customText: qsTr("Erase")
};
const actionFunc = function() {
device.updater.factoryReset();
}
confirmationDialog.openWithMessage(actionFunc, messageObj);
confirmationDialog.openWithMessage(Backend.factoryReset, messageObj);
}
function reinstallFirmware() {
@ -330,11 +329,7 @@ AbstractOverlay {
message: qsTr("Current firmware version will be reinstalled")
};
const actionFunc = function() {
device.updater.fullUpdate(firmwareUpdates.latestVersion);
}
confirmationDialog.openWithMessage(actionFunc, messageObj);
confirmationDialog.openWithMessage(Backend.mainAction, messageObj);
}
function baseName(fileUrl) {

View File

@ -10,14 +10,6 @@ import QFlipper 1.0
Item {
id: mainWindow
enum WindowState {
NoDevice,
Ready,
Updating,
Streaming,
SelfUpdating
}
signal expandStarted
signal expandFinished
signal collapseStarted
@ -29,40 +21,17 @@ Item {
readonly property int baseWidth: 830
readonly property int baseHeight: 500
// TODO: remember log height
readonly property int logHeight: 200
readonly property int minimumLogHeight: 200
readonly property int shadowSize: 16
readonly property int shadowOffset: 4
readonly property var device: deviceRegistry.currentDevice
readonly property var device: Backend.currentDevice
readonly property var deviceState: device ? device.state : undefined
readonly property var deviceInfo: deviceState ? deviceState.info : undefined
readonly property int windowState: {
if(app.updater.state != AppUpdater.Idle) {
MainWindow.SelfUpdating
} else if(!deviceState ) {
MainWindow.NoDevice
} else if(deviceState.isPersistent) {
MainWindow.Updating
} else if(streamOverlay.visible) {
MainWindow.Streaming
} else {
MainWindow.Ready
}
}
onWindowStateChanged: {
if(windowState !== MainWindow.NoDevice) {
device.streamer.enabled = (windowState === MainWindow.Ready) ||
(windowState === MainWindow.Streaming);
} else {
streamOverlay.opacity = 0;
}
}
Component.onCompleted: {
if(applicationUpdates.isReady) {
askForSelfUpdate();
@ -111,6 +80,12 @@ Item {
parent: bg
}
SelfUpdateDialog {
id: selfUpdateDialog
radius: bg.radius
parent: bg
}
Rectangle {
id: blackBorder
anchors.fill: parent
@ -135,7 +110,7 @@ Item {
samples: shadowSize * 2 + 1
horizontalOffset: 0
verticalOffset: shadowOffset
color: Qt.rgba(0, 0, 0, 0.3)
color: Qt.rgba(0, 0, 0, 0.7)
}
}
@ -190,44 +165,46 @@ Item {
DeviceWidget {
id: deviceWidget
opacity: (windowState === MainWindow.Streaming) || (windowState === MainWindow.SelfUpdating) ? 0 : 1
x: windowState === MainWindow.Ready ? Math.round(mainContent.width / 2) : 216
opacity: streamOverlay.visible ? 0 : 1
x: Backend.state === Backend.Ready ? Math.round(mainContent.width / 2) : 216
y: 85
onScreenStreamRequested: streamOverlay.opacity = 1
onScreenStreamRequested: Backend.startFullScreenStreaming()
}
NoDeviceOverlay {
id: noDeviceOverlay
anchors.fill: parent
opacity: windowState === MainWindow.NoDevice ? 1 : 0
opacity: Backend.state === Backend.WaitingForDevices ? 1 : 0
}
HomeOverlay {
id: homeOverlay
backgroundRect: bg
anchors.fill: parent
opacity: windowState === MainWindow.Ready ? 1 : 0
opacity: Backend.state === Backend.Ready ? 1 : 0
}
UpdateOverlay {
id: updateOverlay
backgroundRect: bg
anchors.fill: parent
opacity: windowState === MainWindow.Updating ? 1 : 0
opacity: (Backend.state > Backend.ScreenStreaming) &&
(Backend.state < Backend.Finished) ? 1 : 0
}
SelfUpdateOverlay {
id: selfUpdateOverlay
FinishOverlay {
id: finishOverlay
backgroundRect: bg
anchors.fill: parent
opacity: windowState === MainWindow.SelfUpdating ? 1 : 0
opacity: (Backend.state === Backend.Finished) ||
(Backend.state === Backend.ErrorOccured) ? 1 : 0
}
StreamOverlay {
id: streamOverlay
anchors.fill: parent
opacity: 0
opacity: Backend.state === Backend.ScreenStreaming ? 1 : 0
}
}
@ -351,16 +328,7 @@ Item {
function askForSelfUpdate() {
if(app.updater.canUpdate(applicationUpdates.latestVersion)) {
const messageObj = {
title : qsTr("Update qFlipper?"),
message: qsTr("Newer version of qFlipper<br/>will be installed"),
customText: qsTr("Update")
};
confirmationDialog.openWithMessage(function() {
app.updater.installUpdate(applicationUpdates.latestVersion);
}, messageObj);
selfUpdateDialog.open();
}
}
}

View File

@ -0,0 +1,116 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import Theme 1.0
import QFlipper 1.0
CustomDialog {
id: control
closable: false
closePolicy: Popup.NoAutoClose
title: app.updater.state === AppUpdater.Idle ? qsTr("Update qFlipper?") : qsTr("Updating qFlipper")
contentWidget: Item {
implicitWidth: 430
implicitHeight: layout.implicitHeight
ColumnLayout {
id: layout
width: parent.implicitWidth
TextLabel {
id: messageLabel
visible: app.updater.state === AppUpdater.Idle
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: qsTr("Newer version of qFlipper<br/>will be installed")
lineHeight: 1.4
wrapMode: Text.Wrap
Layout.topMargin: 24
Layout.bottomMargin: -8
Layout.fillWidth: true
}
// TODO: Find a way to use a DialogButtonBox properly
RowLayout {
id: buttonBox
visible: app.updater.state === AppUpdater.Idle
spacing: 30
Layout.margins: 20
Layout.fillWidth: true
Layout.preferredHeight: 42
layoutDirection: Qt.platform.os === "osx" ? Qt.RightToLeft : Qt.LeftToRight
SmallButton {
radius: 7
text: qsTr("Update")
highlighted: true
Layout.fillWidth: true
Layout.fillHeight: true
onClicked: app.updater.installUpdate(applicationUpdates.latestVersion);
}
SmallButton {
radius: 7
text: qsTr("Cancel")
Layout.fillWidth: true
Layout.fillHeight: true
onClicked: control.rejected()
}
}
ProgressBar {
id: progressBar
visible: app.updater.state !== AppUpdater.Idle
implicitWidth: 286
implicitHeight: 56
from: 0
to: 100
value: app.updater.progress
indeterminate: value < 0
Layout.topMargin: 40
Layout.bottomMargin: 20
Layout.alignment: Qt.AlignHCenter
}
TextLabel {
id: progressLabel
padding: 0
visible: app.updater.state !== AppUpdater.Idle
text: {
switch(app.updater.state) {
case AppUpdater.Downloading:
return qsTr("Downloading latest version...");
case AppUpdater.Updating:
return qsTr("Starting update process...");
case AppUpdater.ErrorOccured:
return qsTr("Update failed");
default:
return qsTr("Preparing...");
}
}
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
Layout.fillWidth: true
Layout.bottomMargin: 14
color: app.updater.state === AppUpdater.ErrorOccured ? Theme.color.lightred3 : Theme.color.lightorange2
}
}
}
}

View File

@ -1,60 +0,0 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import Theme 1.0
import QFlipper 1.0
AbstractOverlay {
id: overlay
TextLabel {
id: updateLabel
capitalized: false
anchors.horizontalCenter: parent.horizontalCenter
y: 24
color: Theme.color.lightorange2
font.family: "Born2bSportyV2"
font.pixelSize: 48
text: qsTr("Updating qFlipper")
}
ProgressBar {
id: progressBar
width: 280
height: 56
from: 0
to: 100
x: Math.round((parent.width - width) / 2)
y: 265
value: app.updater.progress
indeterminate: value < 0
}
TextLabel {
id: messageLabel
anchors.top: progressBar.bottom
anchors.topMargin: 20
anchors.horizontalCenter: parent.horizontalCenter
text: {
switch(app.updater.state) {
case AppUpdater.Downloading:
return qsTr("Downloading latest version...");
case AppUpdater.Updating:
return qsTr("Starting update process...");
case AppUpdater.ErrorOccured:
return qsTr("Update failed");
default:
return qsTr("Preparing...");
}
}
color: Theme.color.lightorange2
}
}

View File

@ -63,6 +63,9 @@ AbstractOverlay {
Button {
action: backAction
icon.width: 24
icon.height: 24
icon.source: "qrc:/assets/gfx/symbolic/arrow-back.svg"
}
@ -74,8 +77,8 @@ AbstractOverlay {
action: saveAction
Layout.alignment: Qt.AlignRight
icon.height: 20
icon.width: 20
icon.height: 20
icon.source: "qrc:/assets/gfx/symbolic/save-symbolic.svg"
}
}
@ -123,7 +126,7 @@ AbstractOverlay {
id: backAction
text: qsTr("Back")
enabled: overlay.enabled
onTriggered: opacity = 0
onTriggered: Backend.stopFullScreenStreaming()
}
Action {

View File

@ -90,7 +90,6 @@
<file>assets/fonts/ProggySquare.ttf</file>
<file>assets/fonts/ShareTechMono-Regular.ttf</file>
<file>components/TextView.qml</file>
<file>components/SelfUpdateOverlay.qml</file>
<file>components/AbstractOverlay.qml</file>
<file>style/Menu.qml</file>
<file>style/MenuItem.qml</file>
@ -100,5 +99,7 @@
<file>assets/gfx/symbolic/save-symbolic.svg</file>
<file>assets/gfx/symbolic/info-big.svg</file>
<file>assets/gfx/images/streaming-help.svg</file>
<file>components/FinishOverlay.qml</file>
<file>components/SelfUpdateDialog.qml</file>
</qresource>
</RCC>

View File

@ -2,15 +2,15 @@
#include <QTimer>
#include "debug.h"
AbstractOperation::AbstractOperation(QObject *parent):
QObject(parent),
m_timeout(new QTimer(this)),
m_timeoutTimer(new QTimer(this)),
m_operationState(BasicOperationState::Ready)
{
connect(m_timeout, &QTimer::timeout, this, &AbstractOperation::onOperationTimeout);
m_timeout->setSingleShot(true);
connect(m_timeoutTimer, &QTimer::timeout, this, &AbstractOperation::onOperationTimeout);
m_timeoutTimer->setSingleShot(true);
m_timeoutTimer->setInterval(10000);
}
void AbstractOperation::finish()
@ -26,6 +26,11 @@ int AbstractOperation::operationState() const
return m_operationState;
}
void AbstractOperation::setTimeout(int msec)
{
m_timeoutTimer->setInterval(msec);
}
void AbstractOperation::onOperationTimeout()
{
finishWithError(QStringLiteral("Operation timeout (generic)"));
@ -42,12 +47,12 @@ void AbstractOperation::finishWithError(const QString &errorMsg)
finish();
}
void AbstractOperation::startTimeout(int msec)
void AbstractOperation::startTimeout()
{
m_timeout->start(msec);
m_timeoutTimer->start();
}
void AbstractOperation::stopTimeout()
{
m_timeout->stop();
m_timeoutTimer->stop();
}

View File

@ -25,6 +25,7 @@ public:
virtual void finish();
int operationState() const;
void setTimeout(int msec);
signals:
void started();
@ -33,14 +34,14 @@ signals:
protected slots:
virtual void onOperationTimeout();
void startTimeout();
void stopTimeout();
protected:
void setOperationState(int state);
void finishWithError(const QString &errorMsg);
void startTimeout(int msec = 10000);
void stopTimeout();
private:
QTimer *m_timeout;
QTimer *m_timeoutTimer;
int m_operationState;
};

View File

@ -6,8 +6,6 @@
#include "abstractoperation.h"
#define CALL_LATER(obj, func) (QTimer::singleShot(0, obj, func))
Q_LOGGING_CATEGORY(CATEGORY_DEFAULT, "DEFAULT")
AbstractOperationRunner::AbstractOperationRunner(QObject *parent):
@ -15,30 +13,6 @@ AbstractOperationRunner::AbstractOperationRunner(QObject *parent):
m_state(State::Idle)
{}
bool AbstractOperationRunner::onQueueStarted()
{
//Default empty implementation
return true;
}
bool AbstractOperationRunner::onQueueFinished()
{
//Default empty implementation
return true;
}
void AbstractOperationRunner::onOperationStarted(AbstractOperation *operation)
{
Q_UNUSED(operation);
//Default empty implementation
}
void AbstractOperationRunner::onOperationFinished(AbstractOperation *operation)
{
Q_UNUSED(operation);
//Default empty implementation
}
const QLoggingCategory &AbstractOperationRunner::loggingCategory() const
{
return CATEGORY_DEFAULT();
@ -48,13 +22,7 @@ void AbstractOperationRunner::enqueueOperation(AbstractOperation *operation)
{
if(m_state == State::Idle) {
m_state = State::Running;
if(!onQueueStarted()) {
setError(QStringLiteral("Failed to start the operation queue"));
return;
}
CALL_LATER(this, &AbstractOperationRunner::processQueue);
QTimer::singleShot(0, this, &AbstractOperationRunner::processQueue);
}
m_queue.enqueue(operation);
@ -64,11 +32,6 @@ void AbstractOperationRunner::processQueue()
{
if(m_queue.isEmpty()) {
m_state = State::Idle;
if(!onQueueFinished()) {
setError(QStringLiteral("Failed to finish the operation queue"));
}
return;
}
@ -82,18 +45,16 @@ void AbstractOperationRunner::processQueue()
processQueue();
} else {
CALL_LATER(this, &AbstractOperationRunner::processQueue);
QTimer::singleShot(0, this, &AbstractOperationRunner::processQueue);
}
qCInfo(loggingCategory()).noquote() << operation->description() << (operation->isError() ? QStringLiteral("ERROR: ") + operation->errorString() : QStringLiteral("SUCCESS"));
onOperationFinished(operation);
qCInfo(loggingCategory()).noquote() << operation->description() << (operation->isError() ? QStringLiteral("ERROR: ") +
operation->errorString() : QStringLiteral("SUCCESS"));
operation->deleteLater();
});
qCInfo(loggingCategory()).noquote() << operation->description() << "START";
onOperationStarted(operation);
operation->start();
}

View File

@ -21,11 +21,8 @@ public:
AbstractOperationRunner(QObject *parent = nullptr);
protected:
virtual bool onQueueStarted();
virtual bool onQueueFinished();
virtual void onOperationStarted(AbstractOperation *operation);
virtual void onOperationFinished(AbstractOperation *operation);
template<class T>
T* registerOperation(T* operation);
virtual const QLoggingCategory &loggingCategory() const;
@ -40,3 +37,10 @@ private:
OperationQueue m_queue;
};
template<class T>
T *AbstractOperationRunner::registerOperation(T *operation)
{
enqueueOperation(operation);
return operation;
}

View File

@ -0,0 +1,181 @@
#pragma once
#include <QSerialPort>
#include <pb.h>
#include <pb_encode.h>
#include <pb_decode.h>
template<typename Msg>
class AbstractProtobufMessage
{
public:
AbstractProtobufMessage(QSerialPort *serialPort);
virtual ~AbstractProtobufMessage() {}
protected:
QSerialPort *serialPort() const;
Msg *pbMessage();
const Msg *pbMessage() const;
private:
QSerialPort *m_serialPort;
Msg m_message;
};
template<const pb_msgdesc_t *MsgDesc, typename Msg>
class AbstractProtobufRequest : public AbstractProtobufMessage<Msg>
{
public:
AbstractProtobufRequest(QSerialPort *serialPort);
virtual ~AbstractProtobufRequest() {}
bool send();
qint64 bytesWritten() const;
private:
static bool outputCallback(pb_ostream_t *stream, const pb_byte_t *buf, size_t count);
qint64 m_bytesWritten;
};
template<const pb_msgdesc_t *MsgDesc, typename Msg>
class AbstractProtobufResponse : public AbstractProtobufMessage<Msg>
{
public:
AbstractProtobufResponse(QSerialPort *serialPort);
virtual ~AbstractProtobufResponse();
bool receive();
bool isComplete() const;
void release();
private:
static bool inputCallback(pb_istream_t *stream, pb_byte_t *buf, size_t count);
bool m_isComplete;
};
template<typename Msg>
AbstractProtobufMessage<Msg>::AbstractProtobufMessage(QSerialPort *serialPort):
m_serialPort(serialPort)
{
memset(&m_message, 0, sizeof(Msg));
}
template<typename Msg>
QSerialPort *AbstractProtobufMessage<Msg>::serialPort() const
{
return m_serialPort;
}
template<typename Msg>
Msg *AbstractProtobufMessage<Msg>::pbMessage()
{
return &m_message;
}
template<typename Msg>
const Msg *AbstractProtobufMessage<Msg>::pbMessage() const
{
return &m_message;
}
template<const pb_msgdesc_t *MsgDesc, typename Msg>
AbstractProtobufRequest<MsgDesc, Msg>::AbstractProtobufRequest(QSerialPort *serialPort):
AbstractProtobufMessage<Msg>(serialPort),
m_bytesWritten(0)
{}
template<const pb_msgdesc_t *MsgDesc, typename Msg>
bool AbstractProtobufRequest<MsgDesc, Msg>::send()
{
auto *serialPort = AbstractProtobufMessage<Msg>::serialPort();
pb_ostream_t ostream {
outputCallback,
serialPort,
SIZE_MAX,
0,
nullptr
};
const auto success = pb_encode_ex(&ostream, MsgDesc, AbstractProtobufMessage<Msg>::pbMessage(),
PB_ENCODE_DELIMITED) && serialPort->flush();
m_bytesWritten = success ? ostream.bytes_written : -1;
return success;
}
template<const pb_msgdesc_t *MsgDesc, typename Msg>
qint64 AbstractProtobufRequest<MsgDesc, Msg>::bytesWritten() const
{
return m_bytesWritten;
}
template<const pb_msgdesc_t *MsgDesc, typename Msg>
bool AbstractProtobufRequest<MsgDesc, Msg>::outputCallback(pb_ostream_t *stream, const pb_byte_t *buf, size_t count)
{
auto *serialPort = (QSerialPort*)stream->state;
return serialPort->write((const char*)buf, count) == (qint64)count;
}
template<const pb_msgdesc_t *MsgDesc, typename Msg>
AbstractProtobufResponse<MsgDesc, Msg>::AbstractProtobufResponse(QSerialPort *serialPort):
AbstractProtobufMessage<Msg> (serialPort),
m_isComplete(false)
{}
template<const pb_msgdesc_t *MsgDesc, typename Msg>
AbstractProtobufResponse<MsgDesc, Msg>::~AbstractProtobufResponse()
{
release();
}
template<const pb_msgdesc_t *MsgDesc, typename Msg>
bool AbstractProtobufResponse<MsgDesc, Msg>::receive()
{
// Allow for re-use: release any previously allocated data
release();
auto *serialPort = AbstractProtobufMessage<Msg>::serialPort();
pb_istream_t istream {
inputCallback,
serialPort,
(size_t)serialPort->bytesAvailable(),
nullptr
};
serialPort->startTransaction();
m_isComplete = pb_decode_ex(&istream, MsgDesc, AbstractProtobufMessage<Msg>::pbMessage(), PB_DECODE_DELIMITED);
if(m_isComplete) {
serialPort->commitTransaction();
} else {
serialPort->rollbackTransaction();
}
return m_isComplete;
}
template<const pb_msgdesc_t *MsgDesc, typename Msg>
bool AbstractProtobufResponse<MsgDesc, Msg>::isComplete() const
{
return m_isComplete;
}
template<const pb_msgdesc_t *MsgDesc, typename Msg>
void AbstractProtobufResponse<MsgDesc, Msg>::release()
{
if(m_isComplete) {
pb_release(MsgDesc, AbstractProtobufMessage<Msg>::pbMessage());
}
}
template<const pb_msgdesc_t *MsgDesc, typename Msg>
bool AbstractProtobufResponse<MsgDesc, Msg>::inputCallback(pb_istream_t *stream, pb_byte_t *buf, size_t count)
{
auto *serialPort = (QSerialPort*)stream->state;
return serialPort->read((char*)buf, count) == (qint64)count;
}

View File

@ -0,0 +1,12 @@
#include "abstractprotobufoperation.h"
#include <QDebug>
#include <QSerialPort>
#include <QLoggingCategory>
AbstractProtobufOperation::AbstractProtobufOperation(QSerialPort *serialPort, QObject *parent):
AbstractSerialOperation(serialPort, parent)
{}
AbstractProtobufOperation::~AbstractProtobufOperation()
{}

View File

@ -0,0 +1,13 @@
#pragma once
#include "abstractserialoperation.h"
class AbstractProtobufOperation : public AbstractSerialOperation
{
Q_OBJECT
public:
AbstractProtobufOperation(QSerialPort *serialPort, QObject *parent = nullptr);
virtual ~AbstractProtobufOperation();
};

View File

@ -3,27 +3,34 @@
#include <QTimer>
#include <QSerialPort>
#include "debug.h"
#define CALL_LATER(obj, func) (QTimer::singleShot(0, obj, func))
AbstractSerialOperation::AbstractSerialOperation(QSerialPort *serialPort, QObject *parent):
AbstractOperation(parent),
m_serialPort(serialPort)
m_serialPort(serialPort),
m_totalBytesWritten(0)
{}
void AbstractSerialOperation::start()
{
connect(m_serialPort, &QSerialPort::readyRead, this, &AbstractSerialOperation::startTimeout);
connect(m_serialPort, &QSerialPort::readyRead, this, &AbstractSerialOperation::onSerialPortReadyRead);
connect(m_serialPort, &QSerialPort::errorOccurred, this, &AbstractSerialOperation::onSerialPortError);
connect(m_serialPort, &QSerialPort::bytesWritten, this, &AbstractSerialOperation::onSerialPortBytesWritten);
CALL_LATER(this, &AbstractSerialOperation::begin);
QTimer::singleShot(0, this, [=]() {
if(!begin()) {
finishWithError(QStringLiteral("Failed to begin operation"));
} else {
startTimeout();
}
});
}
void AbstractSerialOperation::finish()
{
disconnect(m_serialPort, &QSerialPort::readyRead, this, &AbstractSerialOperation::startTimeout);
disconnect(m_serialPort, &QSerialPort::readyRead, this, &AbstractSerialOperation::onSerialPortReadyRead);
disconnect(m_serialPort, &QSerialPort::errorOccurred, this, &AbstractSerialOperation::onSerialPortError);
disconnect(m_serialPort, &QSerialPort::bytesWritten, this, &AbstractSerialOperation::onSerialPortBytesWritten);
AbstractOperation::finish();
}
@ -33,6 +40,32 @@ QSerialPort *AbstractSerialOperation::serialPort() const
return m_serialPort;
}
qint64 AbstractSerialOperation::totalBytesWritten() const
{
return m_totalBytesWritten;
}
void AbstractSerialOperation::resetTotalBytesWritten()
{
m_totalBytesWritten = 0;
}
void AbstractSerialOperation::onSerialPortReadyRead()
{
// Empty default implementation
}
void AbstractSerialOperation::onTotalBytesWrittenChanged()
{
// Empty default implementation
}
void AbstractSerialOperation::onSerialPortBytesWritten(qint64 numBytes)
{
m_totalBytesWritten += numBytes;
onTotalBytesWrittenChanged();
}
void AbstractSerialOperation::onSerialPortError()
{
finishWithError(m_serialPort->errorString());

View File

@ -18,12 +18,19 @@ public:
protected:
QSerialPort *serialPort() const;
qint64 totalBytesWritten() const;
void resetTotalBytesWritten();
private slots:
virtual void onSerialPortReadyRead() = 0;
virtual void onSerialPortReadyRead();
virtual void onTotalBytesWrittenChanged();
void onSerialPortBytesWritten(qint64 numBytes);
void onSerialPortError();
private:
virtual bool begin() = 0;
QSerialPort *m_serialPort;
qint64 m_totalBytesWritten;
};

View File

@ -0,0 +1,226 @@
#include "applicationbackend.h"
#include <QDebug>
#include <QLoggingCategory>
#include "deviceregistry.h"
#include "updateregistry.h"
#include "preferences.h"
#include "flipperupdates.h"
#include "flipperzero/flipperzero.h"
#include "flipperzero/devicestate.h"
#include "flipperzero/assetmanifest.h"
#include "flipperzero/screenstreamer.h"
#include "flipperzero/helper/toplevelhelper.h"
Q_LOGGING_CATEGORY(LOG_BACKEND, "BACKEND")
using namespace Flipper;
using namespace Zero;
ApplicationBackend::ApplicationBackend(QObject *parent):
QObject(parent),
m_deviceRegistry(new DeviceRegistry(this)),
m_firmwareUpdates(new FirmwareUpdates("https://update.flipperzero.one/firmware/directory.json", this)),
m_applicationUpdates(new ApplicationUpdates("https://update.flipperzero.one/qFlipper/directory.json", this)),
m_state(State::WaitingForDevices),
m_updateStatus(UpdateStatus::Unknown)
{
registerMetaTypes();
registerComparators();
initConnections();
}
ApplicationBackend::State ApplicationBackend::state() const
{
return m_state;
}
ApplicationBackend::UpdateStatus ApplicationBackend::updateStatus() const
{
if(!currentDevice() || !m_firmwareUpdates->isReady()) {
return UpdateStatus::Unknown;
}
const auto &latestVersion = m_firmwareUpdates->latestVersion();
if (currentDevice()->canRepair(latestVersion)) {
return UpdateStatus::CanRepair;
} else if(currentDevice()->canUpdate(latestVersion)) {
return UpdateStatus::CanUpdate;
} else if(currentDevice()->canInstall(latestVersion)) {
return UpdateStatus::CanInstall;
} else{
return UpdateStatus::NoUpdates;
}
}
FlipperZero *ApplicationBackend::currentDevice() const
{
return m_deviceRegistry->currentDevice();
}
void ApplicationBackend::mainAction()
{
AbstractOperationHelper *helper;
if(currentDevice()->deviceState()->isRecoveryMode()) {
setState(State::RepairingDevice);
helper = new RepairTopLevelHelper(m_firmwareUpdates, currentDevice(), this);
} else {
setState(State::UpdatingDevice);
helper = new UpdateTopLevelHelper(m_firmwareUpdates, currentDevice(), this);
}
connect(helper, &AbstractOperationHelper::finished, this, [=]() {
if(helper->isError()) {
qCCritical(LOG_BACKEND).noquote() << "Failed to complete the operation:" << helper->errorString();
}
helper->deleteLater();
});
}
void ApplicationBackend::createBackup(const QUrl &directoryUrl)
{
setState(State::CreatingBackup);
currentDevice()->createBackup(directoryUrl);
}
void ApplicationBackend::restoreBackup(const QUrl &directoryUrl)
{
setState(State::RestoringBackup);
currentDevice()->restoreBackup(directoryUrl);
}
void ApplicationBackend::factoryReset()
{
setState(State::FactoryResetting);
currentDevice()->factoryReset();
}
void ApplicationBackend::installFirmware(const QUrl &fileUrl)
{
setState(State::InstallingFirmware);
currentDevice()->installFirmware(fileUrl);
}
void ApplicationBackend::installWirelessStack(const QUrl &fileUrl)
{
setState(State::InstallingWirelessStack);
currentDevice()->installWirelessStack(fileUrl);
}
void ApplicationBackend::installFUS(const QUrl &fileUrl, uint32_t address)
{
setState(State::InstallingFUS);
currentDevice()->installFUS(fileUrl, address);
}
void ApplicationBackend::startFullScreenStreaming()
{
setState(State::ScreenStreaming);
}
void ApplicationBackend::stopFullScreenStreaming()
{
setState(State::Ready);
}
void ApplicationBackend::finalizeOperation()
{
//TODO: clean up all non-online devices here
m_deviceRegistry->cleanupOffline();
if(currentDevice()) {
setState(State::Ready);
} else {
setState(State::WaitingForDevices);
}
}
void ApplicationBackend::onCurrentDeviceChanged()
{
// Should not happen during an ongoing operation
if(m_state > State::ScreenStreaming && m_state != State::Finished) {
setState(State::ErrorOccured);
qCCritical(LOG_BACKEND) << "Current operation was interrupted";
} else if(m_deviceRegistry->currentDevice()) {
// No need to disconnect the old device, as it has been destroyed at this point
connect(currentDevice(), &FlipperZero::operationFinished, this, &ApplicationBackend::onDeviceOperationFinished);
connect(currentDevice(), &FlipperZero::stateChanged, this, &ApplicationBackend::updateStatusChanged);
setState(State::Ready);
} else {
setState(State::WaitingForDevices);
}
}
void ApplicationBackend::onDeviceOperationFinished()
{
// TODO: Some error handling?
if(!currentDevice() || currentDevice()->deviceState()->isError()) {
setState(State::ErrorOccured);
} else {
setState(State::Finished);
}
}
void ApplicationBackend::initConnections()
{
connect(m_deviceRegistry, &DeviceRegistry::currentDeviceChanged, this, &ApplicationBackend::currentDeviceChanged);
connect(m_deviceRegistry, &DeviceRegistry::currentDeviceChanged, this, &ApplicationBackend::onCurrentDeviceChanged);
connect(m_deviceRegistry, &DeviceRegistry::currentDeviceChanged, this, &ApplicationBackend::updateStatusChanged);
connect(m_firmwareUpdates, &UpdateRegistry::latestVersionChanged, this, &ApplicationBackend::updateStatusChanged);
}
void ApplicationBackend::setState(State newState)
{
if(m_state == newState) {
return;
}
m_state = newState;
emit stateChanged();
}
UpdateRegistry *ApplicationBackend::firmwareUpdates() const
{
return m_firmwareUpdates;
}
UpdateRegistry *ApplicationBackend::applicationUpdates() const
{
return m_applicationUpdates;
}
void ApplicationBackend::registerMetaTypes()
{
qRegisterMetaType<Preferences*>("Preferences*");
qRegisterMetaType<Flipper::Updates::FileInfo>("Flipper::Updates::FileInfo");
qRegisterMetaType<Flipper::Updates::VersionInfo>("Flipper::Updates::VersionInfo");
qRegisterMetaType<Flipper::Updates::ChannelInfo>("Flipper::Updates::ChannelInfo");
qRegisterMetaType<Flipper::Zero::DeviceInfo>("Flipper::Zero::DeviceInfo");
qRegisterMetaType<Flipper::Zero::HardwareInfo>("Flipper::Zero::HardwareInfo");
qRegisterMetaType<Flipper::Zero::SoftwareInfo>("Flipper::Zero::SoftwareInfo");
qRegisterMetaType<Flipper::Zero::StorageInfo>("Flipper::Zero::StorageInfo");
qRegisterMetaType<Flipper::FlipperZero*>("Flipper::FlipperZero*");
qRegisterMetaType<Flipper::Zero::DeviceState*>("Flipper::Zero::DeviceState*");
qRegisterMetaType<Flipper::Zero::ScreenStreamer*>("Flipper::Zero::ScreenStreamer*");
qRegisterMetaType<Flipper::Zero::AssetManifest::FileInfo>();
}
void ApplicationBackend::registerComparators()
{
QMetaType::registerComparators<Flipper::Zero::AssetManifest::FileInfo>();
}

View File

@ -0,0 +1,100 @@
#pragma once
#include <QObject>
namespace Flipper {
class FlipperZero;
class DeviceRegistry;
class UpdateRegistry;
class FirmwareUpdates;
class ApplicationUpdates;
}
class ApplicationBackend : public QObject
{
Q_OBJECT
Q_PROPERTY(State state READ state NOTIFY stateChanged)
Q_PROPERTY(UpdateStatus updateStatus READ updateStatus NOTIFY updateStatusChanged)
Q_PROPERTY(Flipper::FlipperZero* currentDevice READ currentDevice NOTIFY currentDeviceChanged)
public:
enum class State {
WaitingForDevices,
Ready,
ScreenStreaming,
UpdatingDevice,
RepairingDevice,
CreatingBackup,
RestoringBackup,
FactoryResetting,
InstallingFirmware,
InstallingWirelessStack,
InstallingFUS,
Finished,
ErrorOccured
};
Q_ENUM(State)
enum class UpdateStatus {
Unknown,
CanUpdate,
CanInstall,
CanRepair,
NoUpdates
};
Q_ENUM(UpdateStatus)
ApplicationBackend(QObject *parent = nullptr);
State state() const;
UpdateStatus updateStatus() const;
Flipper::FlipperZero *currentDevice() const;
Flipper::UpdateRegistry *firmwareUpdates() const;
Flipper::UpdateRegistry *applicationUpdates() const;
/* Actions available from the GUI.
* Applies to the currently active device. */
Q_INVOKABLE void mainAction();
Q_INVOKABLE void createBackup(const QUrl &directoryUrl);
Q_INVOKABLE void restoreBackup(const QUrl &directoryUrl);
Q_INVOKABLE void factoryReset();
Q_INVOKABLE void installFirmware(const QUrl &fileUrl);
Q_INVOKABLE void installWirelessStack(const QUrl &fileUrl);
Q_INVOKABLE void installFUS(const QUrl &fileUrl, uint32_t address);
Q_INVOKABLE void startFullScreenStreaming();
Q_INVOKABLE void stopFullScreenStreaming();
Q_INVOKABLE void finalizeOperation();
signals:
void stateChanged();
void updateStatusChanged();
void currentDeviceChanged();
private slots:
void onCurrentDeviceChanged();
void onDeviceOperationFinished();
private:
static void registerMetaTypes();
static void registerComparators();
void initConnections();
void setState(State newState);
Flipper::DeviceRegistry *m_deviceRegistry;
Flipper::FirmwareUpdates *m_firmwareUpdates;
Flipper::ApplicationUpdates *m_applicationUpdates;
State m_state;
UpdateStatus m_updateStatus;
};

View File

@ -12,25 +12,48 @@ SOURCES += \
abstractoperation.cpp \
abstractoperationhelper.cpp \
abstractoperationrunner.cpp \
abstractprotobufoperation.cpp \
abstractserialoperation.cpp \
applicationbackend.cpp \
deviceregistry.cpp \
filenode.cpp \
flipperupdates.cpp \
flipperzero/assetmanifest.cpp \
flipperzero/cli/deviceinfooperation.cpp \
flipperzero/cli/dfuoperation.cpp \
flipperzero/cli/factoryresetclioperation.cpp \
flipperzero/cli/rebootoperation.cpp \
flipperzero/cli/guistartstreamoperation.cpp \
flipperzero/cli/guistopstreamoperation.cpp \
flipperzero/cli/startrpcoperation.cpp \
flipperzero/cli/stoprpcoperation.cpp \
flipperzero/cli/storageinfooperation.cpp \
flipperzero/cli/storagelistoperation.cpp \
flipperzero/cli/storagemkdiroperation.cpp \
flipperzero/cli/storagereadoperation.cpp \
flipperzero/cli/storageremoveoperation.cpp \
flipperzero/cli/storagestatoperation.cpp \
flipperzero/cli/storagewriteoperation.cpp \
flipperzero/cli/systemdeviceinfooperation.cpp \
flipperzero/cli/systemfactoryresetoperation.cpp \
flipperzero/cli/systemrebootoperation.cpp \
flipperzero/commandinterface.cpp \
flipperzero/cli/skipmotdoperation.cpp \
flipperzero/devicestate.cpp \
flipperzero/factoryinfo.cpp \
flipperzero/firmwareupdater.cpp \
flipperzero/flipperzero.cpp \
flipperzero/helper/deviceinfohelper.cpp \
flipperzero/helper/firmwarehelper.cpp \
flipperzero/helper/radiomanifesthelper.cpp \
flipperzero/helper/scriptshelper.cpp \
flipperzero/helper/serialinithelper.cpp \
flipperzero/helper/toplevelhelper.cpp \
flipperzero/protobuf/guiprotobufmessage.cpp \
flipperzero/protobuf/mainprotobufmessage.cpp \
flipperzero/protobuf/messages/application.pb.c \
flipperzero/protobuf/messages/flipper.pb.c \
flipperzero/protobuf/messages/gui.pb.c \
flipperzero/protobuf/messages/status.pb.c \
flipperzero/protobuf/messages/storage.pb.c \
flipperzero/protobuf/messages/system.pb.c \
flipperzero/protobuf/storageprotobufmessage.cpp \
flipperzero/protobuf/systemprotobufmessage.cpp \
flipperzero/radiomanifest.cpp \
flipperzero/recovery.cpp \
flipperzero/recovery/abstractrecoveryoperation.cpp \
@ -42,12 +65,6 @@ SOURCES += \
flipperzero/recovery/setbootmodeoperation.cpp \
flipperzero/recovery/wirelessstackdownloadoperation.cpp \
flipperzero/recoveryinterface.cpp \
flipperzero/cli/listoperation.cpp \
flipperzero/cli/mkdiroperation.cpp \
flipperzero/cli/readoperation.cpp \
flipperzero/cli/removeoperation.cpp \
flipperzero/cli/statoperation.cpp \
flipperzero/cli/writeoperation.cpp \
flipperzero/screenstreamer.cpp \
flipperzero/toplevel/abstracttopleveloperation.cpp \
flipperzero/toplevel/factoryresetoperation.cpp \
@ -69,7 +86,6 @@ SOURCES += \
gzipuncompressor.cpp \
logger.cpp \
preferences.cpp \
qflipperbackend.cpp \
remotefilefetcher.cpp \
serialfinder.cpp \
simpleserialoperation.cpp \
@ -82,28 +98,52 @@ HEADERS += \
abstractoperation.h \
abstractoperationhelper.h \
abstractoperationrunner.h \
abstractprotobufmessage.h \
abstractprotobufoperation.h \
abstractserialoperation.h \
applicationbackend.h \
deviceregistry.h \
failable.h \
fileinfo.h \
filenode.h \
flipperupdates.h \
flipperzero/assetmanifest.h \
flipperzero/cli/deviceinfooperation.h \
flipperzero/cli/dfuoperation.h \
flipperzero/cli/factoryresetclioperation.h \
flipperzero/cli/rebootoperation.h \
flipperzero/cli/guistartstreamoperation.h \
flipperzero/cli/guistopstreamoperation.h \
flipperzero/cli/startrpcoperation.h \
flipperzero/cli/stoprpcoperation.h \
flipperzero/cli/storageinfooperation.h \
flipperzero/cli/storagelistoperation.h \
flipperzero/cli/storagemkdiroperation.h \
flipperzero/cli/storagereadoperation.h \
flipperzero/cli/storageremoveoperation.h \
flipperzero/cli/storagestatoperation.h \
flipperzero/cli/storagewriteoperation.h \
flipperzero/cli/systemdeviceinfooperation.h \
flipperzero/cli/systemfactoryresetoperation.h \
flipperzero/cli/systemrebootoperation.h \
flipperzero/commandinterface.h \
flipperzero/cli/skipmotdoperation.h \
flipperzero/deviceinfo.h \
flipperzero/devicestate.h \
flipperzero/factoryinfo.h \
flipperzero/firmwareupdater.h \
flipperzero/flipperzero.h \
flipperzero/helper/deviceinfohelper.h \
flipperzero/helper/firmwarehelper.h \
flipperzero/helper/radiomanifesthelper.h \
flipperzero/helper/scriptshelper.h \
flipperzero/helper/serialinithelper.h \
flipperzero/helper/toplevelhelper.h \
flipperzero/protobuf/guiprotobufmessage.h \
flipperzero/protobuf/mainprotobufmessage.h \
flipperzero/protobuf/messages/application.pb.h \
flipperzero/protobuf/messages/flipper.pb.h \
flipperzero/protobuf/messages/gui.pb.h \
flipperzero/protobuf/messages/status.pb.h \
flipperzero/protobuf/messages/storage.pb.h \
flipperzero/protobuf/messages/system.pb.h \
flipperzero/protobuf/storageprotobufmessage.h \
flipperzero/protobuf/systemprotobufmessage.h \
flipperzero/radiomanifest.h \
flipperzero/recovery.h \
flipperzero/recovery/abstractrecoveryoperation.h \
@ -115,12 +155,6 @@ HEADERS += \
flipperzero/recovery/setbootmodeoperation.h \
flipperzero/recovery/wirelessstackdownloadoperation.h \
flipperzero/recoveryinterface.h \
flipperzero/cli/listoperation.h \
flipperzero/cli/mkdiroperation.h \
flipperzero/cli/readoperation.h \
flipperzero/cli/removeoperation.h \
flipperzero/cli/statoperation.h \
flipperzero/cli/writeoperation.h \
flipperzero/screenstreamer.h \
flipperzero/toplevel/abstracttopleveloperation.h \
flipperzero/toplevel/factoryresetoperation.h \
@ -142,7 +176,6 @@ HEADERS += \
gzipuncompressor.h \
logger.h \
preferences.h \
qflipperbackend.h \
remotefilefetcher.h \
serialfinder.h \
signalingfailable.h \
@ -153,15 +186,22 @@ HEADERS += \
updateregistry.h
unix|win32 {
LIBS += -L$$OUT_PWD/../dfu/ -ldfu
LIBS += -L$$OUT_PWD/../dfu/ -ldfu \
-L$$OUT_PWD/../3rdparty/ -l3rdparty
}
win32:!win32-g++ {
PRE_TARGETDEPS += $$OUT_PWD/../dfu/dfu.lib
PRE_TARGETDEPS += $$OUT_PWD/../dfu/dfu.lib \
$$OUT_PWD/../3rdparty/3rdparty.lib
} else:unix|win32-g++ {
PRE_TARGETDEPS += $$OUT_PWD/../dfu/libdfu.a
PRE_TARGETDEPS += $$OUT_PWD/../dfu/libdfu.a \
$$OUT_PWD/../3rdparty/lib3rdparty.a
}
INCLUDEPATH += $$PWD/../dfu
DEPENDPATH += $$PWD/../dfu
INCLUDEPATH += $$PWD/../dfu \
$$PWD/../3rdparty \
$$PWD/../3rdparty/nanopb
DEPENDPATH += $$PWD/../dfu \
$$PWD/../3rdparty

View File

@ -1,18 +1,21 @@
#include "deviceregistry.h"
#include <QDebug>
#include <QMetaObject>
#include <QLoggingCategory>
#include "flipperzero/helper/deviceinfohelper.h"
#include "flipperzero/flipperzero.h"
#include "flipperzero/devicestate.h"
#include "usbdevice.h"
#include "debug.h"
#define FLIPPER_ZERO_VID 0x0483
#define FLIPPER_ZERO_PID_VCP 0x5740
#define FLIPPER_ZERO_PID_DFU 0xdf11
Q_LOGGING_CATEGORY(CAT_DEVREG, "DEVREG");
using namespace Flipper;
DeviceRegistry::DeviceRegistry(QObject *parent):
@ -26,7 +29,7 @@ DeviceRegistry::DeviceRegistry(QObject *parent):
USBDeviceInfo(FLIPPER_ZERO_VID, FLIPPER_ZERO_PID_VCP)
.withManufacturer("Flipper Devices Inc.")
.withProductDescription("Flipper Control Virtual ComPort")
});
});
}
FlipperZero *DeviceRegistry::currentDevice() const
@ -36,14 +39,15 @@ FlipperZero *DeviceRegistry::currentDevice() const
void DeviceRegistry::insertDevice(const USBDeviceInfo &info)
{
check_return_void(info.isValid(), "A new invalid device has been detected, skipping...");
if(!info.isValid()) {
qCCritical(CAT_DEVREG) << "A new invalid device has been detected, skipping...";
if(info.vendorID() == FLIPPER_ZERO_VID) {
auto *fetcher = Zero::AbstractDeviceInfoHelper::create(info, this);
connect(fetcher, &Zero::AbstractDeviceInfoHelper::finished, this, &DeviceRegistry::processDevice);
} else if(info.vendorID() != FLIPPER_ZERO_VID) {
qCCritical(CAT_DEVREG) << "Unexpected device VID and PID";
} else {
error_msg("Unexpected device VID and PID.");
auto *fetcher = Zero::AbstractDeviceInfoHelper::create(info, this);
connect(fetcher, &Zero::AbstractDeviceInfoHelper::finished, this, &DeviceRegistry::processDevice);
}
}
@ -60,7 +64,8 @@ void DeviceRegistry::removeDevice(const USBDeviceInfo &info)
if(!device->deviceState()->isPersistent()) {
m_devices.takeAt(idx)->deleteLater();
emit devicesChanged();
emit deviceCountChanged();
emit currentDeviceChanged();
} else {
device->deviceState()->setOnline(false);
@ -68,6 +73,22 @@ void DeviceRegistry::removeDevice(const USBDeviceInfo &info)
}
}
void DeviceRegistry::cleanupOffline()
{
auto it = std::remove_if(m_devices.begin(), m_devices.end(), [](Flipper::FlipperZero *arg) {
return !arg->deviceState()->isOnline();
});
for(const auto end = m_devices.end(); it != end; ++it) {
qCDebug(CAT_DEVREG).noquote() << "Removed zombie device:" << (*it)->deviceState()->name();
m_devices.erase(it);
emit deviceCountChanged();
(*it)->deleteLater();
}
}
void DeviceRegistry::processDevice()
{
auto *fetcher = qobject_cast<Zero::AbstractDeviceInfoHelper*>(sender());
@ -75,7 +96,7 @@ void DeviceRegistry::processDevice()
fetcher->deleteLater();
if(fetcher->isError()) {
error_msg(QStringLiteral("An error has occured: %1").arg(fetcher->errorString()));
qCCritical(CAT_DEVREG).noquote() << QStringLiteral("An error has occured:") << fetcher->errorString();
return;
}
@ -87,14 +108,17 @@ void DeviceRegistry::processDevice()
if(it != m_devices.end()) {
// Preserving the old instance
(*it)->deviceState()->reset(info);
(*it)->deviceState()->setDeviceInfo(info);
} else {
auto *device = new FlipperZero(info, this);
m_devices.append(device);
emit deviceConnected(device);
emit devicesChanged();
emit deviceCountChanged();
if(m_devices.size() == 1) {
emit currentDeviceChanged();
}
}
}

View File

@ -12,7 +12,7 @@ class FlipperZero;
class DeviceRegistry : public QObject
{
Q_OBJECT
Q_PROPERTY(Flipper::FlipperZero* currentDevice READ currentDevice NOTIFY devicesChanged)
Q_PROPERTY(Flipper::FlipperZero* currentDevice READ currentDevice NOTIFY currentDeviceChanged)
using DeviceList = QVector<FlipperZero*>;
@ -22,12 +22,13 @@ public:
FlipperZero *currentDevice() const;
signals:
void deviceConnected(FlipperZero*);
void devicesChanged();
void currentDeviceChanged();
void deviceCountChanged();
public slots:
void insertDevice(const USBDeviceInfo &info);
void removeDevice(const USBDeviceInfo &info);
void cleanupOffline();
private slots:
void processDevice();

View File

@ -1,118 +0,0 @@
#include "deviceinfooperation.h"
#include <QBuffer>
#include <QSerialPort>
using namespace Flipper;
using namespace Zero;
DeviceInfoOperation::DeviceInfoOperation(QSerialPort *serialPort, QObject *parent):
SimpleSerialOperation(serialPort, parent)
{}
const QString DeviceInfoOperation::description() const
{
return QStringLiteral("Device Info @%1").arg(QString(serialPort()->portName()));
}
const DeviceInfo &DeviceInfoOperation::result() const
{
return m_deviceInfo;
}
QByteArray DeviceInfoOperation::endOfMessageToken() const
{
return QByteArrayLiteral("\r\n\r\n>: \a");
}
QByteArray DeviceInfoOperation::commandLine() const
{
return QByteArrayLiteral("device_info\r\n");
}
bool DeviceInfoOperation::parseReceivedData()
{
QBuffer buf;
if(!buf.open(QIODevice::ReadWrite)) {
finishWithError(buf.errorString());
return false;
}
buf.write(receivedData());
buf.seek(0);
m_deviceInfo.fusVersion = QStringLiteral("0.0.0");
m_deviceInfo.radioVersion = QStringLiteral("0.0.0");
while(buf.canReadLine()) {
parseLine(buf.readLine());
}
buf.close();
return true;
}
void DeviceInfoOperation::parseLine(const QByteArray &line)
{
// TODO: Add more fields
if(line.count(':') != 1) {
return;
}
const auto validx = line.indexOf(':');
const auto key = line.left(validx).trimmed();
const auto value = line.mid(validx + 1).trimmed();
if(key == QByteArrayLiteral("hardware_name")) {
m_deviceInfo.name = value;
} else if(key == QByteArrayLiteral("hardware_target")) {
m_deviceInfo.hardware.target = QStringLiteral("f") + value;
} else if(key == QByteArrayLiteral("hardware_ver")) {
m_deviceInfo.hardware.version = value;
} else if(key == QByteArrayLiteral("hardware_body")) {
m_deviceInfo.hardware.body = QStringLiteral("b") + value;
} else if(key == QByteArrayLiteral("hardware_connect")) {
m_deviceInfo.hardware.connect = QStringLiteral("c") + value;
} else if(key == QByteArrayLiteral("hardware_color")) {
m_deviceInfo.hardware.color = (HardwareInfo::Color)value.toInt();
} else if(key == QByteArrayLiteral("firmware_version")) {
m_deviceInfo.firmware.version = value;
} else if(key == QByteArrayLiteral("firmware_commit")) {
m_deviceInfo.firmware.commit = value;
} else if(key == QByteArrayLiteral("firmware_branch")) {
m_deviceInfo.firmware.branch = value;
} else if(key == QByteArrayLiteral("firmware_build_date")) {
m_deviceInfo.firmware.date = QDate::fromString(value, QStringLiteral("dd-MM-yyyy"));
} else if(key == QByteArrayLiteral("radio_stack_major")) {
auto fields = m_deviceInfo.radioVersion.split('.');
fields.replace(0, value);
m_deviceInfo.radioVersion = fields.join('.');
} else if(key == QByteArrayLiteral("radio_stack_minor")) {
auto fields = m_deviceInfo.radioVersion.split('.');
fields.replace(1, value);
m_deviceInfo.radioVersion = fields.join('.');
} else if(key == QByteArrayLiteral("radio_stack_sub")) {
auto fields = m_deviceInfo.radioVersion.split('.');
fields.replace(2, value);
m_deviceInfo.radioVersion = fields.join('.');
} else if(key == QByteArrayLiteral("radio_fus_major")) {
auto fields = m_deviceInfo.fusVersion.split('.');
fields.replace(0, value);
m_deviceInfo.fusVersion = fields.join('.');
} else if(key == QByteArrayLiteral("radio_fus_minor")) {
auto fields = m_deviceInfo.fusVersion.split('.');
fields.replace(1, value);
m_deviceInfo.fusVersion = fields.join('.');
} else if(key == QByteArrayLiteral("radio_fus_sub")) {
auto fields = m_deviceInfo.fusVersion.split('.');
fields.replace(2, value);
m_deviceInfo.fusVersion = fields.join('.');
} else {}
}

View File

@ -1,30 +0,0 @@
#pragma once
#include "simpleserialoperation.h"
#include "flipperzero/deviceinfo.h"
namespace Flipper {
namespace Zero {
class DeviceInfoOperation : public SimpleSerialOperation
{
Q_OBJECT
public:
DeviceInfoOperation(QSerialPort *serialPort, QObject *parent = nullptr);
const QString description() const override;
const DeviceInfo &result() const;
private:
QByteArray endOfMessageToken() const override;
QByteArray commandLine() const override;
bool parseReceivedData() override;
void parseLine(const QByteArray &line);
DeviceInfo m_deviceInfo;
};
}
}

View File

@ -1,35 +0,0 @@
#include "dfuoperation.h"
#include <QTimer>
#include <QSerialPort>
#define CALL_LATER(obj, func) (QTimer::singleShot(0, obj, func))
using namespace Flipper;
using namespace Zero;
DFUOperation::DFUOperation(QSerialPort *serialPort, QObject *parent):
AbstractSerialOperation(serialPort, parent)
{}
const QString DFUOperation::description() const
{
return QStringLiteral("Boot to recovery @%1").arg(QString(serialPort()->portName()));
}
void DFUOperation::onSerialPortReadyRead()
{
// This operation does not need serial output, discarding it
serialPort()->clear();
}
bool DFUOperation::begin()
{
const auto success = (serialPort()->write("\rdfu\r\n") > 0) && serialPort()->flush();
if(success) {
CALL_LATER(this, &AbstractOperation::finish);
}
return success;
}

View File

@ -1,30 +0,0 @@
#include "factoryresetclioperation.h"
#include <QSerialPort>
using namespace Flipper;
using namespace Zero;
FactoryResetCliOperation::FactoryResetCliOperation(QSerialPort *serialPort, QObject *parent):
SimpleSerialOperation(serialPort, parent)
{}
const QString FactoryResetCliOperation::description() const
{
return QStringLiteral("Factory reset @%1").arg(QString(serialPort()->portName()));
}
QByteArray FactoryResetCliOperation::endOfMessageToken() const
{
return QByteArrayLiteral("Are you sure (y/n)?\r\n");
}
QByteArray FactoryResetCliOperation::commandLine() const
{
return QByteArrayLiteral("factory_reset\r");
}
bool FactoryResetCliOperation::parseReceivedData()
{
return (serialPort()->write("y\r\n") > 0) && serialPort()->flush();
}

View File

@ -1,24 +0,0 @@
#pragma once
#include "simpleserialoperation.h"
namespace Flipper {
namespace Zero {
class FactoryResetCliOperation : public SimpleSerialOperation
{
Q_OBJECT
public:
FactoryResetCliOperation(QSerialPort *serialPort, QObject *parent = nullptr);
const QString description() const override;
private:
QByteArray endOfMessageToken() const override;
QByteArray commandLine() const override;
bool parseReceivedData() override;
};
}
}

View File

@ -0,0 +1,38 @@
#include "guistartstreamoperation.h"
#include <QSerialPort>
#include "flipperzero/protobuf/guiprotobufmessage.h"
using namespace Flipper;
using namespace Zero;
GuiStartStreamOperation::GuiStartStreamOperation(QSerialPort *serialPort, QObject *parent):
AbstractProtobufOperation(serialPort, parent)
{}
const QString GuiStartStreamOperation::description() const
{
return QStringLiteral("Start screen streaming @%1").arg(serialPort()->portName());
}
void GuiStartStreamOperation::onSerialPortReadyRead()
{
MainEmptyResponse response(serialPort());
if(!response.receive()) {
return;
} else if(!response.isOk()) {
finishWithError(QStringLiteral("Device replied with an error response: %1").arg(response.commandStatusString()));
} else if(!response.isValidType()) {
finishWithError(QStringLiteral("Expected empty reply, got something else"));
} else {
finish();
}
}
bool GuiStartStreamOperation::begin()
{
GuiStartScreenStreamRequest request(serialPort());
return request.send();
}

View File

@ -1,16 +1,16 @@
#pragma once
#include "abstractserialoperation.h"
#include "abstractprotobufoperation.h"
namespace Flipper {
namespace Zero {
class DFUOperation : public AbstractSerialOperation
class GuiStartStreamOperation : public AbstractProtobufOperation
{
Q_OBJECT
public:
DFUOperation(QSerialPort *serialPort, QObject *parent = nullptr);
GuiStartStreamOperation(QSerialPort *serialPort, QObject *parent = nullptr);
const QString description() const override;
private slots:

View File

@ -0,0 +1,46 @@
#include "guistopstreamoperation.h"
#include <QSerialPort>
#include "flipperzero/protobuf/guiprotobufmessage.h"
using namespace Flipper;
using namespace Zero;
GuiStopStreamOperation::GuiStopStreamOperation(QSerialPort *serialPort, QObject *parent):
AbstractProtobufOperation(serialPort, parent)
{}
const QString GuiStopStreamOperation::description() const
{
return QStringLiteral("Stop screen streaming @%1").arg(serialPort()->portName());
}
void GuiStopStreamOperation::onSerialPortReadyRead()
{
MainEmptyResponse response(serialPort());
while(response.receive()) {
if(!response.isOk()) {
finishWithError(QStringLiteral("Device replied with an error response: %1").arg(response.commandStatusString()));
} else if(!response.isValidType()) {
if(response.whichContent() != GuiScreenFrameResponse::tag()) {
finishWithError(QStringLiteral("Expected empty or screen frame reply, got something else"));
} else {
continue;
}
} else {
finish();
}
return;
}
}
bool GuiStopStreamOperation::begin()
{
GuiStopScreenStreamRequest request(serialPort());
return request.send();
}

View File

@ -1,16 +1,16 @@
#pragma once
#include "abstractserialoperation.h"
#include "abstractprotobufoperation.h"
namespace Flipper {
namespace Zero {
class RebootOperation : public AbstractSerialOperation
class GuiStopStreamOperation : public AbstractProtobufOperation
{
Q_OBJECT
public:
RebootOperation(QSerialPort *serialPort, QObject *parent = nullptr);
GuiStopStreamOperation(QSerialPort *serialPort, QObject *parent = nullptr);
const QString description() const override;
private slots:

View File

@ -1,85 +0,0 @@
#include "listoperation.h"
#include <QBuffer>
#include "debug.h"
#define FILE_PREFIX QByteArrayLiteral("[F]")
#define DIRECTORY_PREFIX QByteArrayLiteral("[D]")
using namespace Flipper;
using namespace Zero;
ListOperation::ListOperation(QSerialPort *serialPort, const QByteArray &dirName, QObject *parent):
SimpleSerialOperation(serialPort, parent),
m_dirName(dirName)
{}
const QString ListOperation::description() const
{
return QStringLiteral("List @%1").arg(QString(m_dirName));
}
const FileInfoList &ListOperation::result() const
{
return m_result;
}
QByteArray ListOperation::endOfMessageToken() const
{
return QByteArrayLiteral("\r\n\r\n>: \a");
}
QByteArray ListOperation::commandLine() const
{
return QByteArrayLiteral("storage list \"") + m_dirName + QByteArrayLiteral("\"\r\n");
}
bool ListOperation::parseReceivedData()
{
QBuffer buf;
if(!buf.open(QIODevice::ReadWrite)) {
finishWithError(buf.errorString());
return false;
}
buf.write(receivedData());
buf.seek(0);
while(buf.canReadLine()) {
const auto line = buf.readLine().trimmed();
if(line.startsWith(FILE_PREFIX)) {
parseFile(line);
} else if(line.startsWith(DIRECTORY_PREFIX)) {
parseDirectory(line);
} else {}
}
return true;
}
void ListOperation::parseDirectory(const QByteArray &line)
{
FileInfo info;
info.name = line.mid(DIRECTORY_PREFIX.size() + 1);
info.absolutePath = m_dirName + QByteArrayLiteral("/") + info.name;
info.type = FileType::Directory;
info.size = 0;
m_result.append(info);
}
void ListOperation::parseFile(const QByteArray &line)
{
const auto sizeIdx = line.lastIndexOf(' ') + 1;
const auto nameIdx = FILE_PREFIX.size() + 1;
FileInfo info;
info.name = line.mid(nameIdx, sizeIdx - nameIdx - 1);
info.absolutePath = m_dirName + QByteArrayLiteral("/") + info.name;
info.type = FileType::RegularFile;
info.size = line.mid(sizeIdx, line.size() - sizeIdx - 1).toLongLong(nullptr, 10);
m_result.append(info);
}

View File

@ -1,36 +0,0 @@
#pragma once
#include "simpleserialoperation.h"
#include <QList>
#include "fileinfo.h"
namespace Flipper {
namespace Zero {
class ListOperation : public SimpleSerialOperation
{
Q_OBJECT
public:
ListOperation(QSerialPort *serialPort, const QByteArray &dirName, QObject *parent = nullptr);
const QString description() const override;
const FileInfoList &result() const;
private:
QByteArray endOfMessageToken() const override;
QByteArray commandLine() const override;
bool parseReceivedData() override;
void parseDirectory(const QByteArray &line);
void parseFile(const QByteArray &line);
private:
QByteArray m_dirName;
FileInfoList m_result;
};
}
}

View File

@ -1,49 +0,0 @@
#include "mkdiroperation.h"
#include "debug.h"
using namespace Flipper;
using namespace Zero;
MkDirOperation::MkDirOperation(QSerialPort *serialPort, const QByteArray &dirName, QObject *parent):
SimpleSerialOperation(serialPort, parent),
m_dirName(dirName)
{}
const QString MkDirOperation::description() const
{
return QStringLiteral("MkDir @%1").arg(QString(m_dirName));
}
QByteArray MkDirOperation::endOfMessageToken() const
{
return QByteArrayLiteral("\r\n\r\n>: \a");
}
QByteArray MkDirOperation::commandLine() const
{
return QByteArrayLiteral("storage mkdir \"") + m_dirName + QByteArrayLiteral("\"\r\n");
}
bool MkDirOperation::parseReceivedData()
{
const auto lines = receivedData().split('\n');
if(lines.size() == 4) {
const auto msg = lines.at(1).trimmed();
if(!msg.startsWith(QByteArrayLiteral("Storage error: "))) {
return false;
} else if(!msg.endsWith(QByteArrayLiteral("file/dir already exist"))) {
setError(msg);
} else {
debug_msg(QStringLiteral("Warning: directory %1 already exists.").arg(QString(m_dirName)));
}
return true;
} else if(lines.size() == 3) {
return true;
} else {
return false;
}
}

View File

@ -1,27 +0,0 @@
#pragma once
#include "simpleserialoperation.h"
namespace Flipper {
namespace Zero {
class MkDirOperation : public SimpleSerialOperation
{
Q_OBJECT
public:
MkDirOperation(QSerialPort *serialPort, const QByteArray &dirName, QObject *parent = nullptr);
const QString description() const override;
private:
QByteArray endOfMessageToken() const override;
QByteArray commandLine() const override;
bool parseReceivedData() override;
QByteArray m_dirName;
};
}
}

View File

@ -1,127 +0,0 @@
#include "readoperation.h"
#include <QSerialPort>
#define READY_PROMPT QByteArrayLiteral("\r\nReady?\r\n")
#define FINISH_PROMPT QByteArrayLiteral("\r\n\r\n>: ")
#define READY_PROMPT_LINE_COUNT 5
#define FINISH_PROMPT_LINE_COUNT 4
#define CHUNK_SIZE 512
using namespace Flipper;
using namespace Zero;
ReadOperation::ReadOperation(QSerialPort *serialPort, const QByteArray &fileName, QIODevice *file, QObject *parent):
AbstractSerialOperation(serialPort, parent),
m_size(0),
m_fileName(fileName),
m_file(file)
{}
const QString ReadOperation::description() const
{
return QStringLiteral("Read @%1").arg(QString(m_fileName));
}
void ReadOperation::onSerialPortReadyRead()
{
startTimeout();
m_receivedData += serialPort()->readAll();
if(operationState() == State::SettingUp) {
if(m_receivedData.endsWith(READY_PROMPT)) {
if(!parseSetupReply()) {
finish();
} else {
setOperationState(State::ReceivingData);
serialPort()->write("\n");
}
m_receivedData.clear();
} else if(m_receivedData.endsWith(FINISH_PROMPT)) {
parseError();
finish();
}
} else if(operationState() == State::ReceivingData) {
if(m_receivedData.endsWith(READY_PROMPT)) {
m_file->write(m_receivedData.chopped(READY_PROMPT.size()));
m_receivedData.clear();
serialPort()->write("\n");
} else if(m_receivedData.endsWith(FINISH_PROMPT)) {
m_file->write(m_receivedData.chopped(FINISH_PROMPT.size()));
m_file->seek(0);
finish();
}
}
}
bool ReadOperation::begin()
{
const auto cmdLine = QByteArrayLiteral("storage read_chunks \"") + m_fileName + QByteArrayLiteral("\" ") +
QByteArray::number(CHUNK_SIZE, 10) + QByteArrayLiteral("\r");
const auto success = (serialPort()->write(cmdLine) == cmdLine.size()) && serialPort()->flush();
if(success) {
setOperationState(State::SettingUp);
startTimeout();
}
return success;
}
bool ReadOperation::parseError()
{
const auto lines = m_receivedData.split('\n');
if(lines.size() != FINISH_PROMPT_LINE_COUNT) {
setError(QStringLiteral("Unexpected error message line count"));
return false;
}
const auto &msg = lines.at(1).trimmed();
if(!msg.startsWith("Storage error:")) {
setError(QStringLiteral("Unexpected error message format"));
return false;
}
setError(msg);
return true;
}
bool ReadOperation::parseSetupReply()
{
const auto lines = m_receivedData.split('\n');
if(lines.size() != READY_PROMPT_LINE_COUNT) {
setError(QStringLiteral("Unexpected setup message line count"));
return false;
}
const auto &sizeMsg = lines.at(1);
if(!sizeMsg.startsWith("Size:")) {
setError(QStringLiteral("Unexpected setup message format"));
return false;
}
return parseSize(sizeMsg);
}
bool ReadOperation::parseSize(const QByteArray &s)
{
const auto tokens = s.split(':');
if(tokens.size() != 2) {
setError(QStringLiteral("Unexpected size message format"));
return false;
}
bool success;
m_size = tokens.at(1).toLongLong(&success, 10);
return success;
}

View File

@ -1,43 +0,0 @@
#pragma once
#include "abstractserialoperation.h"
#include <QByteArray>
class QIODevice;
namespace Flipper {
namespace Zero {
class ReadOperation : public AbstractSerialOperation
{
Q_OBJECT
enum State {
SettingUp = BasicOperationState::Ready,
ReceivingData
};
public:
ReadOperation(QSerialPort *serialPort, const QByteArray &fileName, QIODevice *file, QObject *parent = nullptr);
const QString description() const override;
private slots:
void onSerialPortReadyRead() override;
private:
bool begin() override;
bool parseError();
bool parseSetupReply();
bool parseSize(const QByteArray &s);
qint64 m_size;
QByteArray m_fileName;
QIODevice *m_file;
QByteArray m_receivedData;
};
}
}

View File

@ -1,35 +0,0 @@
#include "rebootoperation.h"
#include <QTimer>
#include <QSerialPort>
#define CALL_LATER(obj, func) (QTimer::singleShot(0, obj, func))
using namespace Flipper;
using namespace Zero;
RebootOperation::RebootOperation(QSerialPort *serialPort, QObject *parent):
AbstractSerialOperation(serialPort, parent)
{}
const QString RebootOperation::description() const
{
return QStringLiteral("Reboot @%1").arg(QString(serialPort()->portName()));
}
void RebootOperation::onSerialPortReadyRead()
{
// This operation does not need serial output, discarding it
serialPort()->clear();
}
bool RebootOperation::begin()
{
const auto success = (serialPort()->write("\rreboot\r\n") > 0) && serialPort()->flush();
if(success) {
CALL_LATER(this, &AbstractOperation::finish);
}
return success;
}

View File

@ -1,50 +0,0 @@
#include "removeoperation.h"
#include "debug.h"
using namespace Flipper;
using namespace Zero;
RemoveOperation::RemoveOperation(QSerialPort *serialPort, const QByteArray &fileName, QObject *parent):
SimpleSerialOperation(serialPort, parent),
m_fileName(fileName)
{}
const QString RemoveOperation::description() const
{
return QStringLiteral("Remove @%1").arg(QString(m_fileName));
}
QByteArray RemoveOperation::endOfMessageToken() const
{
return QByteArrayLiteral("\r\n\r\n>: \a");
}
QByteArray RemoveOperation::commandLine() const
{
return QByteArrayLiteral("storage remove \"") + m_fileName + QByteArrayLiteral("\"\r\n");
}
bool RemoveOperation::parseReceivedData()
{
const auto lines = receivedData().split('\n');
if(lines.size() == 4) {
const auto msg = lines.at(1).trimmed();
if(!msg.startsWith(QByteArrayLiteral("Storage error: "))) {
return false;
} else if(!msg.endsWith(QByteArrayLiteral("file/dir not exist"))) {
setError(msg);
} else {
debug_msg(QStringLiteral("Warning: file %1 does not exist.").arg(QString(m_fileName)));
}
return true;
} else if(lines.size() == 3) {
return true;
} else {
return false;
}
}

View File

@ -1,27 +0,0 @@
#pragma once
#include "simpleserialoperation.h"
namespace Flipper {
namespace Zero {
class RemoveOperation : public SimpleSerialOperation
{
Q_OBJECT
public:
RemoveOperation(QSerialPort *serialPort, const QByteArray &fileName, QObject *parent = nullptr);
const QString description() const override;
private:
QByteArray endOfMessageToken() const override;
QByteArray commandLine() const override;
bool parseReceivedData() override;
QByteArray m_fileName;
};
}
}

View File

@ -0,0 +1,66 @@
#include "startrpcoperation.h"
#include <QSerialPort>
#include "flipperzero/protobuf/systemprotobufmessage.h"
using namespace Flipper;
using namespace Zero;
StartRPCOperation::StartRPCOperation(QSerialPort *serialPort, QObject *parent):
AbstractProtobufOperation(serialPort, parent)
{}
const QString StartRPCOperation::description() const
{
return QStringLiteral("Start RPC session @%1").arg(QString(serialPort()->portName()));
}
void StartRPCOperation::onSerialPortReadyRead()
{
if(operationState() == State::LeavingCli) {
if(serialPort()->bytesAvailable() < s_cmd.length() + 1) {
return;
}
const auto str = serialPort()->readAll();
if(!str.startsWith(s_cmd)) {
finishWithError(QStringLiteral("Failed to send the command due to interference"));
return;
}
SystemPingRequest request(serialPort());
if(!request.send()) {
finishWithError(QStringLiteral("Failed to send the ping request"));
} else {
setOperationState(State::WaitingForPing);
}
} else if(operationState() == State::WaitingForPing) {
SystemPingResponse response(serialPort());
if(!response.receive()) {
return;
} else if(!response.isOk()) {
finishWithError(QStringLiteral("Device replied with error: %1").arg(response.commandStatusString()));
} else if(!response.isValidType()) {
finishWithError(QStringLiteral("Expected system ping response, got something else"));
} else {
finish();
}
} else {
finishWithError(QStringLiteral("Received data in an unexpected state"));
}
}
bool StartRPCOperation::begin()
{
setOperationState(State::LeavingCli);
return (serialPort()->write(s_cmd) == s_cmd.size()) && serialPort()->flush();
}
const QByteArray StartRPCOperation::s_cmd("start_rpc_session\r");

View File

@ -0,0 +1,31 @@
#pragma once
#include "abstractprotobufoperation.h"
namespace Flipper {
namespace Zero {
class StartRPCOperation : public AbstractProtobufOperation
{
Q_OBJECT
enum State {
LeavingCli = AbstractOperation::User,
WaitingForPing
};
public:
StartRPCOperation(QSerialPort *serialPort, QObject *parent = nullptr);
const QString description() const override;
private slots:
void onSerialPortReadyRead() override;
private:
bool begin() override;
static const QByteArray s_cmd;
};
}
}

View File

@ -1,132 +0,0 @@
#include "statoperation.h"
#include <QRegExp>
#include <QSerialPort>
#include "debug.h"
using namespace Flipper;
using namespace Zero;
static qint64 fromStringSize(const QByteArray &s);
StatOperation::StatOperation(QSerialPort *serialPort, const QByteArray &fileName, QObject *parent):
SimpleSerialOperation(serialPort, parent),
m_fileName(fileName),
m_size(-1),
m_sizeFree(-1),
m_type(Type::Invalid)
{}
const QString StatOperation::description() const
{
return QStringLiteral("Stat @%1").arg(QString(m_fileName));
}
const QByteArray &StatOperation::fileName() const
{
return m_fileName;
}
qint64 StatOperation::size() const
{
return m_size;
}
qint64 StatOperation::sizeFree() const
{
return m_sizeFree;
}
StatOperation::Type StatOperation::type() const
{
return m_type;
}
QByteArray StatOperation::endOfMessageToken() const
{
return QByteArrayLiteral("\r\n\r\n>: \a");
}
QByteArray StatOperation::commandLine() const
{
return QByteArrayLiteral("storage stat \"") + m_fileName + QByteArrayLiteral("\"\r\n");
}
bool StatOperation::parseReceivedData()
{
const auto lines = receivedData().split('\n');
check_return_bool(lines.size() == 4, "Wrong reply line count.");
const auto reply = lines[1].trimmed().toLower();
if(reply == "storage error: file/dir not exist") {
m_type = Type::NotFound;
} else if(reply == "storage error: invalid name/path") {
m_type = Type::Invalid;
} else if(reply == "storage error: internal error") {
m_type = Type::InternalError;
} else if(parseFileSize(reply)) {
m_type = Type::RegularFile;
} else if(reply.contains("directory")) {
m_type = Type::Directory;
} else if(parseStorageSize(reply)) {
m_type = Type::Storage;
} else {
error_msg("Unexpected stat reply string.");
return false;
}
return true;
}
bool StatOperation::parseFileSize(const QByteArray &data)
{
QRegExp expr("file, size: ([0-9]+k?i?b)");
if(!expr.exactMatch(data) || (expr.captureCount() != 1)) {
return false;
}
const auto captures = expr.capturedTexts();
m_size = fromStringSize(captures[1].toLocal8Bit());
return m_size >= 0;
}
bool StatOperation::parseStorageSize(const QByteArray &data)
{
QRegExp expr("storage, ([0-9]+k?i?b) total, ([0-9]+k?i?b) free");
if(!expr.exactMatch(data) || (expr.captureCount() != 2)) {
return false;
}
const auto captures = expr.capturedTexts();
m_size = fromStringSize(captures[1].toLocal8Bit());
m_sizeFree = fromStringSize(captures[2].toLocal8Bit());
return (m_size >= 0) && (m_sizeFree >= 0);
}
static qint64 fromStringSize(const QByteArray &s)
{
qint64 result = -1;
bool cr = true;
if(s.endsWith("kb")) {
result = s.chopped(2).toULongLong(&cr, 10) * 1024; //TODO: fix incorrect multipliers in the firmware?
} else if(s.endsWith("kib")) {
result = s.chopped(3).toULongLong(&cr, 10) * 1024;
} else if(s.endsWith('b')) {
result = s.chopped(1).toULongLong(&cr, 10);
}
return cr ? result : -1;
}

View File

@ -1,49 +0,0 @@
#pragma once
#include "simpleserialoperation.h"
namespace Flipper {
namespace Zero {
class StatOperation : public SimpleSerialOperation
{
Q_OBJECT
public:
enum class Type {
RegularFile,
Directory,
Storage,
NotFound,
InternalError,
Invalid
};
Q_ENUM(Type)
StatOperation(QSerialPort *serialPort, const QByteArray &fileName, QObject *parent = nullptr);
const QString description() const override;
const QByteArray &fileName() const;
qint64 size() const;
qint64 sizeFree() const;
Type type() const;
private:
QByteArray endOfMessageToken() const override;
QByteArray commandLine() const override;
bool parseReceivedData() override;
bool parseFileSize(const QByteArray &data);
bool parseStorageSize(const QByteArray &data);
QByteArray m_fileName;
qint64 m_size;
qint64 m_sizeFree;
Type m_type;
};
}
}

View File

@ -0,0 +1,31 @@
#include "stoprpcoperation.h"
#include <QSerialPort>
#include "flipperzero/protobuf/mainprotobufmessage.h"
using namespace Flipper;
using namespace Zero;
StopRPCOperation::StopRPCOperation(QSerialPort *serialPort, QObject *parent):
AbstractProtobufOperation(serialPort, parent)
{}
const QString StopRPCOperation::description() const
{
return QStringLiteral("Stop RPC session @%1").arg(QString(serialPort()->portName()));
}
void StopRPCOperation::onSerialPortReadyRead()
{
m_receivedData.append(serialPort()->readAll());
if(m_receivedData.endsWith(QByteArrayLiteral("\r\n>: "))) {
finish();
}
}
bool StopRPCOperation::begin()
{
MainStopSessionRequest request(serialPort());
return request.send();
}

View File

@ -0,0 +1,27 @@
#pragma once
#include "abstractprotobufoperation.h"
namespace Flipper {
namespace Zero {
class StopRPCOperation : public AbstractProtobufOperation
{
Q_OBJECT
public:
StopRPCOperation(QSerialPort *serialPort, QObject *parent = nullptr);
const QString description() const override;
private slots:
void onSerialPortReadyRead() override;
private:
bool begin() override;
QByteArray m_receivedData;
};
}
}

View File

@ -0,0 +1,68 @@
#include "storageinfooperation.h"
#include "flipperzero/protobuf/storageprotobufmessage.h"
using namespace Flipper;
using namespace Zero;
StorageInfoOperation::StorageInfoOperation(QSerialPort *serialPort, const QByteArray &path, QObject *parent):
AbstractProtobufOperation(serialPort, parent),
m_path(path),
m_isPresent(false),
m_sizeFree(0),
m_sizeTotal(0)
{}
const QString StorageInfoOperation::description() const
{
return QStringLiteral("Storage info @%1").arg(QString(m_path));
}
bool StorageInfoOperation::isPresent() const
{
return m_isPresent;
}
quint64 StorageInfoOperation::sizeFree() const
{
return m_sizeFree;
}
quint64 StorageInfoOperation::sizeTotal() const
{
return m_sizeTotal;
}
void StorageInfoOperation::onSerialPortReadyRead()
{
StorageInfoResponse response(serialPort());
if(!response.receive()) {
return;
} else if(!response.isOk()) {
const auto status = response.commandStatus();
// TODO: more flexible error handling
if(status == PB_CommandStatus_ERROR_STORAGE_INTERNAL) {
finish();
} else{
finishWithError(QStringLiteral("Device replied with error: %1").arg(response.commandStatusString()));
}
} else if(!response.isValidType()) {
finishWithError(QStringLiteral("Expected StorageInfo response, got something else"));
} else {
m_isPresent = true;
m_sizeFree = response.sizeFree();
m_sizeTotal = response.sizeTotal();
finish();
}
}
bool StorageInfoOperation::begin()
{
StorageInfoRequest request(serialPort(), m_path);
return request.send();
}

View File

@ -0,0 +1,34 @@
#pragma once
#include "abstractprotobufoperation.h"
namespace Flipper {
namespace Zero {
class StorageInfoOperation : public AbstractProtobufOperation
{
Q_OBJECT
public:
StorageInfoOperation(QSerialPort *serialPort, const QByteArray &path, QObject *parent = nullptr);
const QString description() const override;
bool isPresent() const;
quint64 sizeFree() const;
quint64 sizeTotal() const;
private slots:
void onSerialPortReadyRead() override;
private:
bool begin() override;
QByteArray m_path;
bool m_isPresent;
quint64 m_sizeFree;
quint64 m_sizeTotal;
};
}
}

View File

@ -0,0 +1,62 @@
#include "storagelistoperation.h"
#include "flipperzero/protobuf/storageprotobufmessage.h"
using namespace Flipper;
using namespace Zero;
StorageListOperation::StorageListOperation(QSerialPort *serialPort, const QByteArray &path, QObject *parent):
AbstractSerialOperation(serialPort, parent),
m_path(path)
{}
const QString StorageListOperation::description() const
{
return QStringLiteral("Storage list @%1").arg(QString(m_path));
}
const FileInfoList &StorageListOperation::files() const
{
return m_result;
}
void StorageListOperation::onSerialPortReadyRead()
{
StorageListResponse response(serialPort());
while(response.receive()) {
if(!response.isOk()) {
finishWithError(QStringLiteral("Device replied with error: %1").arg(response.commandStatusString()));
return;
} else if(!response.isValidType()) {
finishWithError(QStringLiteral("Expected StorageList response, got something else"));
return;
}
const auto &files = response.files();
for(auto &file : files) {
FileInfo fileInfo {
QByteArray(file.name),
m_path + QByteArrayLiteral("/") + QByteArray(file.name),
file.type == PB_Storage_File_FileType_FILE ? FileType::RegularFile : FileType::Directory,
file.size
};
m_result.append(fileInfo);
}
if(!response.hasNext()) {
finish();
return;
}
}
}
bool StorageListOperation::begin()
{
StorageListRequest request(serialPort(), m_path);
return request.send();
}

View File

@ -0,0 +1,30 @@
#pragma once
#include "abstractserialoperation.h"
#include "fileinfo.h"
namespace Flipper {
namespace Zero {
class StorageListOperation : public AbstractSerialOperation
{
Q_OBJECT
public:
StorageListOperation(QSerialPort *serialPort, const QByteArray &path, QObject *parent = nullptr);
const QString description() const override;
const FileInfoList &files() const;
private slots:
void onSerialPortReadyRead() override;
private:
bool begin() override;
QByteArray m_path;
FileInfoList m_result;
};
}
}

View File

@ -0,0 +1,36 @@
#include "storagemkdiroperation.h"
#include "flipperzero/protobuf/storageprotobufmessage.h"
using namespace Flipper;
using namespace Zero;
StorageMkdirOperation::StorageMkdirOperation(QSerialPort *serialPort, const QByteArray &path, QObject *parent):
AbstractProtobufOperation(serialPort, parent),
m_path(path)
{}
const QString StorageMkdirOperation::description() const
{
return QStringLiteral("Storage mkdir @%1").arg(QString(m_path));
}
void StorageMkdirOperation::onSerialPortReadyRead()
{
MainEmptyResponse response(serialPort());
if(!response.receive()) {
return;
} else if(!response.isOk() && (response.commandStatus() != PB_CommandStatus_ERROR_STORAGE_EXIST)) {
finishWithError(QStringLiteral("Device replied with error: %1").arg(response.commandStatusString()));
} else if(!response.isValidType()) {
finishWithError(QStringLiteral("Expected empty response, got something else"));
} else {
finish();
}
}
bool StorageMkdirOperation::begin()
{
StorageMkdirRequest request(serialPort(), m_path);
return request.send();
}

View File

@ -0,0 +1,28 @@
#pragma once
#include "abstractprotobufoperation.h"
namespace Flipper {
namespace Zero {
class StorageMkdirOperation : public AbstractProtobufOperation
{
Q_OBJECT
public:
StorageMkdirOperation(QSerialPort *serialPort, const QByteArray &path, QObject *parent = nullptr);
const QString description() const override;
private slots:
void onSerialPortReadyRead() override;
private:
bool begin() override;
QByteArray m_path;
};
}
}

View File

@ -0,0 +1,58 @@
#include "storagereadoperation.h"
#include "flipperzero/protobuf/storageprotobufmessage.h"
using namespace Flipper;
using namespace Zero;
StorageReadOperation::StorageReadOperation(QSerialPort *serialPort, const QByteArray &path, QIODevice *file, QObject *parent):
AbstractProtobufOperation(serialPort, parent),
m_path(path),
m_file(file)
{}
const QString StorageReadOperation::description() const
{
return QStringLiteral("Storage read @%1").arg(QString(m_path));
}
void StorageReadOperation::onSerialPortReadyRead()
{
StorageReadResponse response(serialPort());
while(response.receive()) {
if(!response.isOk()) {
finishWithError(QStringLiteral("Device replied with error: %1").arg(response.commandStatusString()));
} else if(!response.isValidType()) {
finishWithError(QStringLiteral("Expected StorageRead response, got something else"));
} else {
const auto &data = response.data();
const auto bytesWritten = m_file->write(response.data());
if(bytesWritten != data.size()) {
finishWithError(QStringLiteral("Error writing to output device: %1").arg(m_file->errorString()));
} else if(!response.hasNext()) {
rewindAndFinish();
} else {
continue;
}
}
break;
}
}
bool StorageReadOperation::begin()
{
StorageReadRequest request(serialPort(), m_path);
return request.send();
}
void StorageReadOperation::rewindAndFinish()
{
if(!m_file->seek(0)) {
finishWithError(QStringLiteral("Failed to rewind output device: %1").arg(m_file->errorString()));
} else {
finish();
}
}

View File

@ -0,0 +1,33 @@
#pragma once
#include "abstractprotobufoperation.h"
#include <QByteArray>
class QIODevice;
namespace Flipper {
namespace Zero {
class StorageReadOperation : public AbstractProtobufOperation
{
Q_OBJECT
public:
StorageReadOperation(QSerialPort *serialPort, const QByteArray &path, QIODevice *file, QObject *parent = nullptr);
const QString description() const override;
private slots:
void onSerialPortReadyRead() override;
private:
bool begin() override;
void rewindAndFinish();
QByteArray m_path;
QIODevice *m_file;
};
}
}

View File

@ -0,0 +1,37 @@
#include "storageremoveoperation.h"
#include "flipperzero/protobuf/storageprotobufmessage.h"
using namespace Flipper;
using namespace Zero;
StorageRemoveOperation::StorageRemoveOperation(QSerialPort *serialPort, const QByteArray &path, QObject *parent):
AbstractProtobufOperation(serialPort, parent),
m_path(path)
{}
const QString StorageRemoveOperation::description() const
{
return QStringLiteral("Storage remove @%1").arg(QString(m_path));
}
void StorageRemoveOperation::onSerialPortReadyRead()
{
MainEmptyResponse response(serialPort());
if(!response.receive()) {
return;
} else if(!response.isOk()) {
finishWithError(QStringLiteral("Device replied with error: %1").arg(response.commandStatusString()));
} else if(!response.isValidType()) {
finishWithError(QStringLiteral("Expected empty response, got something else"));
} else {
finish();
}
}
bool StorageRemoveOperation::begin()
{
StorageRemoveRequest request(serialPort(), m_path);
return request.send();
}

View File

@ -0,0 +1,27 @@
#pragma once
#include "abstractprotobufoperation.h"
namespace Flipper {
namespace Zero {
class StorageRemoveOperation : public AbstractProtobufOperation
{
Q_OBJECT
public:
StorageRemoveOperation(QSerialPort *serialPort, const QByteArray &path, QObject *parent = nullptr);
const QString description() const override;
private slots:
void onSerialPortReadyRead() override;
private:
bool begin() override;
QByteArray m_path;
};
}
}

View File

@ -0,0 +1,73 @@
#include "storagestatoperation.h"
#include "flipperzero/protobuf/storageprotobufmessage.h"
using namespace Flipper;
using namespace Zero;
StorageStatOperation::StorageStatOperation(QSerialPort *serialPort, const QByteArray &fileName, QObject *parent):
AbstractProtobufOperation(serialPort, parent),
m_fileName(fileName),
m_isPresent(false),
m_size(0),
m_type(Type::Invalid)
{}
const QString StorageStatOperation::description() const
{
return QStringLiteral("Storage stat @%1").arg(QString(m_fileName));
}
const QByteArray &StorageStatOperation::fileName() const
{
return m_fileName;
}
bool StorageStatOperation::isPresent() const
{
return m_isPresent;
}
quint64 StorageStatOperation::size() const
{
return m_size;
}
StorageStatOperation::Type StorageStatOperation::type() const
{
return m_type;
}
void StorageStatOperation::onSerialPortReadyRead()
{
StorageStatResponse response(serialPort());
if(!response.receive()) {
return;
} else if(!response.isOk()) {
const auto status = response.commandStatus();
// TODO: more flexible error handling
if(status == PB_CommandStatus_ERROR_STORAGE_NOT_EXIST) {
finish();
} else{
finishWithError(QStringLiteral("Device replied with error: %1").arg(response.commandStatusString()));
}
} else if(!response.isValidType()) {
finishWithError(QStringLiteral("Expected StorageStat response, got something else"));
} else {
m_isPresent = response.isPresent();
m_type = (response.file().type == PB_Storage_File_FileType_FILE) ? Type::RegularFile : Type::Directory;
m_size = response.file().size;
finish();
}
}
bool StorageStatOperation::begin()
{
StorageStatRequest request(serialPort(), m_fileName);
return request.send();
}

View File

@ -0,0 +1,43 @@
#pragma once
#include "abstractprotobufoperation.h"
namespace Flipper {
namespace Zero {
class StorageStatOperation : public AbstractProtobufOperation
{
Q_OBJECT
public:
enum class Type {
RegularFile,
Directory,
Invalid
};
Q_ENUM(Type)
StorageStatOperation(QSerialPort *serialPort, const QByteArray &fileName, QObject *parent = nullptr);
const QString description() const override;
const QByteArray &fileName() const;
bool isPresent() const;
quint64 size() const;
Type type() const;
private:
void onSerialPortReadyRead() override;
private:
bool begin() override;
QByteArray m_fileName;
bool m_isPresent;
quint64 m_size;
Type m_type;
};
}
}

View File

@ -0,0 +1,74 @@
#include "storagewriteoperation.h"
#include <QTimer>
#include <QIODevice>
#include "flipperzero/protobuf/storageprotobufmessage.h"
static constexpr qint64 CHUNK_SIZE = 512;
using namespace Flipper;
using namespace Zero;
StorageWriteOperation::StorageWriteOperation(QSerialPort *serialPort, const QByteArray &path, QIODevice *file, QObject *parent):
AbstractProtobufOperation(serialPort, parent),
m_path(path),
m_file(file),
m_byteCount(0)
{}
const QString StorageWriteOperation::description() const
{
return QStringLiteral("Storage write @%1").arg(QString(m_path));
}
void StorageWriteOperation::onSerialPortReadyRead()
{
MainEmptyResponse response(serialPort());
if(!response.receive()) {
return;
} else if(!response.isOk()) {
finishWithError(QStringLiteral("Device replied with error: %1").arg(response.commandStatusString()));
} else if(!response.isValidType()) {
finishWithError(QStringLiteral("Expected empty response, got something else"));
} else {
finish();
}
}
void StorageWriteOperation::onTotalBytesWrittenChanged()
{
if(totalBytesWritten() != m_byteCount) {
return;
}
const auto bytesAvailable = m_file->bytesAvailable();
if(bytesAvailable < 0) {
finishWithError(QStringLiteral("Failed to read from input device: %1").arg(m_file->errorString()));
} else if(bytesAvailable > 0) {
// Must write the chunk asynchronously in order to receive this signal
QTimer::singleShot(0, this, [=]() {
if(!writeChunk()) {
finishWithError(QStringLiteral("Failed to write chunk"));
}
});
}
}
bool StorageWriteOperation::begin()
{
return writeChunk();
}
bool StorageWriteOperation::writeChunk()
{
const auto hasNext = m_file->bytesAvailable() > CHUNK_SIZE;
StorageWriteRequest request(serialPort(), m_path, m_file->read(CHUNK_SIZE), hasNext);
const auto success = request.send();
m_byteCount += request.bytesWritten();
return success;
}

View File

@ -0,0 +1,35 @@
#pragma once
#include "abstractprotobufoperation.h"
#include <QByteArray>
class QIODevice;
namespace Flipper {
namespace Zero {
class StorageWriteOperation : public AbstractProtobufOperation
{
Q_OBJECT
public:
StorageWriteOperation(QSerialPort *serialPort, const QByteArray &path, QIODevice *file, QObject *parent = nullptr);
const QString description() const override;
private slots:
void onSerialPortReadyRead() override;
void onTotalBytesWrittenChanged() override;
private:
bool begin() override;
bool writeChunk();
QByteArray m_path;
QIODevice *m_file;
qint64 m_byteCount;
};
}
}

View File

@ -0,0 +1,51 @@
#include "systemdeviceinfooperation.h"
#include <QSerialPort>
#include "flipperzero/protobuf/systemprotobufmessage.h"
using namespace Flipper;
using namespace Zero;
SystemDeviceInfoOperation::SystemDeviceInfoOperation(QSerialPort *serialPort, QObject *parent):
AbstractProtobufOperation(serialPort, parent)
{}
const QString SystemDeviceInfoOperation::description() const
{
return QStringLiteral("Device Info @%1").arg(QString(serialPort()->portName()));
}
const QByteArray SystemDeviceInfoOperation::result(const QByteArray &key) const
{
return m_data.value(key);
}
void SystemDeviceInfoOperation::onSerialPortReadyRead()
{
SystemDeviceInfoResponse response(serialPort());
while(response.receive()) {
if(!response.isOk()) {
finishWithError(QStringLiteral("Device replied with an error response"));
return;
} else if(!response.isValidType()) {
finishWithError(QStringLiteral("Expected empty reply, got something else"));
return;
}
m_data.insert(response.key(), response.value());
if(!response.hasNext()) {
finish();
return;
}
}
}
bool SystemDeviceInfoOperation::begin()
{
SystemDeviceInfoRequest request(serialPort());
return request.send();
}

View File

@ -0,0 +1,32 @@
#pragma once
#include "abstractprotobufoperation.h"
#include <QHash>
#include <QByteArray>
#include "flipperzero/deviceinfo.h"
namespace Flipper {
namespace Zero {
class SystemDeviceInfoOperation : public AbstractProtobufOperation
{
Q_OBJECT
public:
SystemDeviceInfoOperation(QSerialPort *serialPort, QObject *parent = nullptr);
const QString description() const override;
const QByteArray result(const QByteArray &key) const;
private slots:
void onSerialPortReadyRead() override;
private:
bool begin() override;
QHash<QByteArray, QByteArray> m_data;
};
}
}

View File

@ -0,0 +1,33 @@
#include "systemfactoryresetoperation.h"
#include <QSerialPort>
#include "flipperzero/protobuf/systemprotobufmessage.h"
using namespace Flipper;
using namespace Zero;
SystemFactoryResetOperation::SystemFactoryResetOperation(QSerialPort *serialPort, QObject *parent):
AbstractProtobufOperation(serialPort, parent),
m_byteCount(0)
{}
const QString SystemFactoryResetOperation::description() const
{
return QStringLiteral("Factory reset @%1").arg(QString(serialPort()->portName()));
}
void SystemFactoryResetOperation::onTotalBytesWrittenChanged()
{
if(m_byteCount == totalBytesWritten()) {
finish();
}
}
bool SystemFactoryResetOperation::begin()
{
SystemFactoryResetRequest request(serialPort());
const auto success = request.send();
m_byteCount = request.bytesWritten();
return success;
}

View File

@ -0,0 +1,26 @@
#pragma once
#include "abstractprotobufoperation.h"
namespace Flipper {
namespace Zero {
class SystemFactoryResetOperation : public AbstractProtobufOperation
{
Q_OBJECT
public:
SystemFactoryResetOperation(QSerialPort *serialPort, QObject *parent = nullptr);
const QString description() const override;
private slots:
void onTotalBytesWrittenChanged() override;
private:
bool begin() override;
qint64 m_byteCount;
};
}
}

View File

@ -0,0 +1,37 @@
#include "systemrebootoperation.h"
#include <QTimer>
#include <QSerialPort>
#include "flipperzero/protobuf/systemprotobufmessage.h"
using namespace Flipper;
using namespace Zero;
SystemRebootOperation::SystemRebootOperation(QSerialPort *serialPort, RebootType rebootType, QObject *parent):
AbstractProtobufOperation(serialPort, parent),
m_rebootType(rebootType),
m_byteCount(0)
{}
const QString SystemRebootOperation::description() const
{
return QStringLiteral("System reboot @%1").arg(QString(serialPort()->portName()));
}
void SystemRebootOperation::onTotalBytesWrittenChanged()
{
if(m_byteCount == totalBytesWritten()) {
finish();
}
}
bool SystemRebootOperation::begin()
{
const auto rebootType = m_rebootType == RebootType::OS ? PB_System_RebootRequest_RebootMode_OS :
PB_System_RebootRequest_RebootMode_DFU;
SystemRebootRequest request(serialPort(), rebootType);
const auto success = request.send();
m_byteCount = request.bytesWritten();
return success;
}

View File

@ -0,0 +1,33 @@
#pragma once
#include "abstractprotobufoperation.h"
namespace Flipper {
namespace Zero {
class SystemRebootOperation : public AbstractProtobufOperation
{
Q_OBJECT
public:
enum class RebootType {
OS,
Recovery
};
SystemRebootOperation(QSerialPort *serialPort, RebootType rebootType, QObject *parent = nullptr);
const QString description() const override;
private slots:
void onTotalBytesWrittenChanged() override;
private:
bool begin() override;
RebootType m_rebootType;
qint64 m_byteCount;
};
}
}

View File

@ -1,122 +0,0 @@
#include "writeoperation.h"
#include <QIODevice>
#include <QSerialPort>
#include "debug.h"
#define READY_PROMPT QByteArrayLiteral("\r\nReady\r\n")
#define FINISH_PROMPT QByteArrayLiteral("\r\n>: ")
#define FINISH_PROMPT_LINE_COUNT 4
#define CHUNK_SIZE 512
using namespace Flipper;
using namespace Zero;
WriteOperation::WriteOperation(QSerialPort *serialPort, const QByteArray &fileName, QIODevice *file, QObject *parent):
AbstractSerialOperation(serialPort, parent),
m_fileName(fileName),
m_file(file)
{}
const QString WriteOperation::description() const
{
return QStringLiteral("Write @%1").arg(QString(m_fileName));
}
void WriteOperation::onSerialPortReadyRead()
{
m_receivedData.append(serialPort()->readAll());
if(operationState() == State::SettingUp) {
if(m_receivedData.endsWith(FINISH_PROMPT)) {
parseError();
finish();
} else if(!m_receivedData.endsWith(READY_PROMPT)) {
return;
}
setOperationState(State::WritingData);
if(!writeChunk()) {
finishWithError(QStringLiteral("Failed to write chunk"));
}
} else if(operationState() == State::WritingData) {
if(!m_receivedData.endsWith(FINISH_PROMPT)) {
return;
}
setOperationState(State::SettingUp);
if(!m_file->bytesAvailable()) {
finish();
} else if(!writeSetupCommand()) {
finishWithError(QStringLiteral("Failed to write chunk"));
}
} else {
finishWithError(QStringLiteral("Unexpected data"));
}
m_receivedData.clear();
}
bool WriteOperation::begin()
{
check_return_bool(m_file->bytesAvailable(), "No data is available for reading from file");
setOperationState(State::SettingUp);
return writeSetupCommand();
}
bool WriteOperation::writeSetupCommand()
{
const auto bytesAvailable = m_file->bytesAvailable();
m_chunkSize = bytesAvailable < CHUNK_SIZE ? bytesAvailable : CHUNK_SIZE;
const auto cmdLine = QByteArrayLiteral("storage write_chunk \"") + m_fileName + QByteArrayLiteral("\" ") +
QByteArray::number(m_chunkSize) + QByteArrayLiteral("\r");
const auto success = (serialPort()->write(cmdLine) == cmdLine.size()) && serialPort()->flush();
if(success) {
startTimeout();
}
return success;
}
bool WriteOperation::writeChunk()
{
const auto data = m_file->read(m_chunkSize);
const auto success = (serialPort()->write(data) == data.size()) && serialPort()->flush();
if(success) {
startTimeout();
}
return success;
}
bool WriteOperation::parseError()
{
const auto lines = m_receivedData.split('\n');
if(lines.size() != FINISH_PROMPT_LINE_COUNT) {
setError(QStringLiteral("Unexpected error message line count"));
return false;
}
const auto &msg = lines.at(1).trimmed();
if(!msg.startsWith("Storage error:")) {
setError(QStringLiteral("Unexpected error message format"));
return false;
}
setError(msg);
return true;
}

View File

@ -1,44 +0,0 @@
#pragma once
#include "abstractserialoperation.h"
#include <QByteArray>
class QIODevice;
namespace Flipper {
namespace Zero {
class WriteOperation : public AbstractSerialOperation
{
Q_OBJECT
enum State {
SettingUp = BasicOperationState::Ready,
WritingData
};
public:
WriteOperation(QSerialPort *serialPort, const QByteArray &fileName, QIODevice *file, QObject *parent = nullptr);
const QString description() const override;
private slots:
void onSerialPortReadyRead() override;
private:
bool begin() override;
bool writeSetupCommand();
bool writeChunk();
bool parseError();
QByteArray m_fileName;
QIODevice *m_file;
QByteArray m_receivedData;
qint64 m_chunkSize;
};
}
}

View File

@ -1,131 +1,114 @@
#include "commandinterface.h"
#include <QSerialPort>
#include <QLoggingCategory>
#include "flipperzero/devicestate.h"
#include "cli/factoryresetclioperation.h"
#include "cli/skipmotdoperation.h"
#include "cli/rebootoperation.h"
#include "cli/removeoperation.h"
#include "cli/mkdiroperation.h"
#include "cli/writeoperation.h"
#include "cli/readoperation.h"
#include "cli/statoperation.h"
#include "cli/listoperation.h"
#include "cli/dfuoperation.h"
#include "debug.h"
#include "cli/startrpcoperation.h"
#include "cli/stoprpcoperation.h"
Q_LOGGING_CATEGORY(CATEGORY_CLI, "CLI");
#include "cli/systemrebootoperation.h"
#include "cli/systemfactoryresetoperation.h"
#include "cli/storageremoveoperation.h"
#include "cli/storagemkdiroperation.h"
#include "cli/storagewriteoperation.h"
#include "cli/storagereadoperation.h"
#include "cli/storagelistoperation.h"
#include "cli/storagestatoperation.h"
#include "cli/storageinfooperation.h"
#include "cli/guistartstreamoperation.h"
#include "cli/guistopstreamoperation.h"
Q_LOGGING_CATEGORY(CATEGORY_RPC, "RPC");
using namespace Flipper;
using namespace Zero;
CommandInterface::CommandInterface(DeviceState *state, QObject *parent):
CommandInterface::CommandInterface(DeviceState *deviceState, QObject *parent):
AbstractOperationRunner(parent),
m_serialPort(nullptr)
m_deviceState(deviceState)
{}
QSerialPort *CommandInterface::serialPort() const
{
// Automatically re-create serial port instance when a persistent device reconnects
const auto createSerialPort = [=]() {
if(m_serialPort) {
check_continue(!m_serialPort->isOpen(), "Deleting a Serial Port instance that is still open.");
m_serialPort->deleteLater();
m_serialPort = nullptr;
}
const auto &portInfo = state->deviceInfo().serialInfo;
if(!portInfo.isNull()) {
m_serialPort = new QSerialPort(portInfo, this);
}
};
connect(state, &DeviceState::deviceInfoChanged, this, createSerialPort);
createSerialPort();
return m_deviceState->serialPort();
}
RebootOperation *CommandInterface::reboot()
StopRPCOperation *CommandInterface::stopRPCSession()
{
auto *op = new RebootOperation(m_serialPort, this);
enqueueOperation(op);
return op;
return registerOperation(new StopRPCOperation(serialPort(), this));
}
DFUOperation *CommandInterface::startRecoveryMode()
StartRPCOperation *CommandInterface::startRPCSession()
{
auto *op = new DFUOperation(m_serialPort, this);
enqueueOperation(op);
return op;
return registerOperation(new StartRPCOperation(serialPort(), this));
}
FactoryResetCliOperation *CommandInterface::factoryReset()
SystemRebootOperation *CommandInterface::rebootToOS()
{
auto *op = new FactoryResetCliOperation(m_serialPort, this);
enqueueOperation(op);
return op;
return registerOperation(new SystemRebootOperation(serialPort(), SystemRebootOperation::RebootType::OS, this));
}
ListOperation *CommandInterface::list(const QByteArray &dirName)
SystemRebootOperation *CommandInterface::rebootToRecovery()
{
auto *op = new ListOperation(m_serialPort, dirName, this);
enqueueOperation(op);
return op;
return registerOperation(new SystemRebootOperation(serialPort(), SystemRebootOperation::RebootType::Recovery, this));
}
StatOperation *CommandInterface::stat(const QByteArray &fileName)
SystemFactoryResetOperation *CommandInterface::factoryReset()
{
auto *op = new StatOperation(m_serialPort, fileName, this);
enqueueOperation(op);
return op;
return registerOperation(new SystemFactoryResetOperation(serialPort(), this));
}
ReadOperation *CommandInterface::read(const QByteArray &fileName, QIODevice *file)
StorageListOperation *CommandInterface::storageList(const QByteArray &path)
{
auto *op = new ReadOperation(m_serialPort, fileName, file, this);
enqueueOperation(op);
return op;
return registerOperation(new StorageListOperation(serialPort(), path, this));
}
MkDirOperation *CommandInterface::mkdir(const QByteArray &dirName)
StorageInfoOperation *CommandInterface::storageInfo(const QByteArray &path)
{
auto *op = new MkDirOperation(m_serialPort, dirName, this);
enqueueOperation(op);
return op;
return registerOperation(new StorageInfoOperation(serialPort(), path, this));
}
WriteOperation *CommandInterface::write(const QByteArray &fileName, QIODevice *file)
StorageStatOperation *CommandInterface::storageStat(const QByteArray &path)
{
auto *op = new WriteOperation(m_serialPort, fileName, file, this);
enqueueOperation(op);
return op;
return registerOperation(new StorageStatOperation(serialPort(), path, this));
}
RemoveOperation *CommandInterface::remove(const QByteArray &fileName)
StorageReadOperation *CommandInterface::storageRead(const QByteArray &path, QIODevice *file)
{
auto *op = new RemoveOperation(m_serialPort, fileName, this);
enqueueOperation(op);
return op;
return registerOperation(new StorageReadOperation(serialPort(), path, file, this));
}
bool CommandInterface::onQueueStarted()
StorageMkdirOperation *CommandInterface::storageMkdir(const QByteArray &path)
{
const auto success = m_serialPort->open(QIODevice::ReadWrite);
check_return_bool(success, QStringLiteral("Serial port error: %1").arg(m_serialPort->errorString()));
enqueueOperation(new SkipMOTDOperation(m_serialPort, this));
return true;
return registerOperation(new StorageMkdirOperation(serialPort(), path, this));
}
bool CommandInterface::onQueueFinished()
StorageWriteOperation *CommandInterface::storageWrite(const QByteArray &path, QIODevice *file)
{
m_serialPort->close();
return true;
return registerOperation(new StorageWriteOperation(serialPort(), path, file, this));
}
GuiStartStreamOperation *CommandInterface::guiStartStreaming()
{
return registerOperation(new GuiStartStreamOperation(serialPort(), this));
}
GuiStopStreamOperation *CommandInterface::guiStopStreaming()
{
return registerOperation(new GuiStopStreamOperation(serialPort(), this));
}
StorageRemoveOperation *CommandInterface::storageRemove(const QByteArray &path)
{
return registerOperation(new StorageRemoveOperation(serialPort(), path, this));
}
const QLoggingCategory &CommandInterface::loggingCategory() const
{
return CATEGORY_CLI();
return CATEGORY_RPC();
}

View File

@ -4,48 +4,58 @@
class QIODevice;
class QSerialPort;
class QSerialPortInfo;
namespace Flipper {
namespace Zero {
class DeviceState;
class DFUOperation;
class ListOperation;
class StatOperation;
class ReadOperation;
class MkDirOperation;
class WriteOperation;
class RemoveOperation;
class RebootOperation;
class FactoryResetCliOperation;
class StopRPCOperation;
class StartRPCOperation;
class SystemRebootOperation;
class SystemFactoryResetOperation;
class StorageListOperation;
class StorageInfoOperation;
class StorageStatOperation;
class StorageReadOperation;
class StorageMkdirOperation;
class StorageWriteOperation;
class StorageRemoveOperation;
class GuiStartStreamOperation;
class GuiStopStreamOperation;
class CommandInterface : public AbstractOperationRunner
{
Q_OBJECT
public:
CommandInterface(DeviceState *state, QObject *parent = nullptr);
CommandInterface(DeviceState *deviceState, QObject *parent = nullptr);
QSerialPort *serialPort() const;
RebootOperation *reboot();
DFUOperation *startRecoveryMode();
FactoryResetCliOperation *factoryReset();
StopRPCOperation *stopRPCSession();
StartRPCOperation *startRPCSession();
ListOperation *list(const QByteArray &dirName);
StatOperation *stat(const QByteArray &fileName);
ReadOperation *read(const QByteArray &fileName, QIODevice *file);
MkDirOperation *mkdir(const QByteArray &dirName);
WriteOperation *write(const QByteArray &fileName, QIODevice *file);
RemoveOperation *remove(const QByteArray &fileName);
SystemRebootOperation *rebootToOS();
SystemRebootOperation *rebootToRecovery();
SystemFactoryResetOperation *factoryReset();
StorageListOperation *storageList(const QByteArray &path);
StorageInfoOperation *storageInfo(const QByteArray &path);
StorageStatOperation *storageStat(const QByteArray &path);
StorageMkdirOperation *storageMkdir(const QByteArray &path);
StorageRemoveOperation *storageRemove(const QByteArray &path);
StorageReadOperation *storageRead(const QByteArray &path, QIODevice *file);
StorageWriteOperation *storageWrite(const QByteArray &path, QIODevice *file);
GuiStartStreamOperation *guiStartStreaming();
GuiStopStreamOperation *guiStopStreaming();
private:
bool onQueueStarted() override;
bool onQueueFinished() override;
const QLoggingCategory &loggingCategory() const override;
QSerialPort *m_serialPort;
DeviceState *m_deviceState;
};
}

View File

@ -6,6 +6,8 @@
#include "usbdeviceinfo.h"
class QSerialPort;
namespace Flipper {
namespace Zero {
@ -40,12 +42,14 @@ struct SoftwareInfo {
Q_PROPERTY(QString version MEMBER version)
Q_PROPERTY(QString commit MEMBER commit)
Q_PROPERTY(QString branch MEMBER branch)
Q_PROPERTY(QString channel MEMBER channel)
Q_PROPERTY(QDate date MEMBER date)
public:
QString version;
QString commit;
QString branch;
QString channel;
QDate date;
// Needed in order to work with QVariant
@ -83,7 +87,6 @@ struct DeviceInfo {
Q_PROPERTY(Flipper::Zero::StorageInfo storage MEMBER storage)
public:
QString name;
QString model;
@ -98,7 +101,7 @@ public:
QString systemLocation;
USBDeviceInfo usbInfo;
QSerialPortInfo serialInfo;
QSerialPortInfo portInfo;
};
}

View File

@ -1,23 +1,25 @@
#include "devicestate.h"
#include <QSerialPort>
#include "helper/serialinithelper.h"
using namespace Flipper;
using namespace Zero;
DeviceState::DeviceState(const DeviceInfo &deviceInfo, QObject *parent):
QObject(parent),
m_deviceInfo(deviceInfo),
m_serialPort(nullptr),
m_isPersistent(false),
m_isOnline(true),
m_isOnline(false),
m_isError(false),
m_progress(-1.0)
{}
void DeviceState::reset(const DeviceInfo &newDeviceInfo)
{
setDeviceInfo(newDeviceInfo);
setError(false);
setProgress(-1.0);
setOnline(true);
connect(this, &DeviceState::deviceInfoChanged, this, &DeviceState::onDeviceInfoChanged);
connect(this, &DeviceState::isOnlineChanged, this, &DeviceState::onIsOnlineChanged);
onDeviceInfoChanged();
}
const DeviceInfo &DeviceState::deviceInfo() const
@ -73,7 +75,7 @@ void DeviceState::setError(bool set)
}
m_isError = set;
emit errorChanged();
emit isErrorChanged();
}
bool DeviceState::isRecoveryMode() const
@ -108,7 +110,7 @@ void DeviceState::setStatusString(const QString &newStatusString)
}
m_statusString = newStatusString;
emit statusChanged();
emit statusStringChanged();
}
const QString &DeviceState::errorString() const
@ -125,10 +127,47 @@ void DeviceState::setErrorString(const QString &newErrorString)
m_errorString = newErrorString;
m_isError = true;
emit errorChanged();
emit isErrorChanged();
}
const QString &DeviceState::name() const
{
return m_deviceInfo.name;
}
QSerialPort *DeviceState::serialPort() const
{
return m_serialPort;
}
void DeviceState::onDeviceInfoChanged()
{
setError(false);
setProgress(-1.0);
if(isRecoveryMode()) {
setOnline(true);
return;
}
auto *helper = new SerialInitHelper(m_deviceInfo.portInfo, this);
connect(helper, &AbstractOperationHelper::finished, this, [=]() {
if(helper->isError()) {
setErrorString(tr("Failed to initialize serial port"));
} else {
m_serialPort = helper->serialPort();
setOnline(true);
}
helper->deleteLater();
});
}
void DeviceState::onIsOnlineChanged()
{
if(!m_isOnline && m_serialPort) {
m_serialPort->deleteLater();
m_serialPort = nullptr;
}
}

View File

@ -15,19 +15,17 @@ class DeviceState : public QObject
Q_PROPERTY(bool isPersistent READ isPersistent NOTIFY isPersistentChanged)
Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged)
Q_PROPERTY(bool isError READ isError NOTIFY errorChanged)
Q_PROPERTY(bool isError READ isError NOTIFY isErrorChanged)
Q_PROPERTY(bool isRecoveryMode READ isRecoveryMode NOTIFY deviceInfoChanged)
Q_PROPERTY(QString statusString READ statusString NOTIFY statusChanged)
Q_PROPERTY(QString errorString READ errorString NOTIFY errorChanged)
Q_PROPERTY(QString statusString READ statusString NOTIFY statusStringChanged)
Q_PROPERTY(QString errorString READ errorString NOTIFY isErrorChanged)
Q_PROPERTY(double progress READ progress NOTIFY progressChanged)
public:
DeviceState(const DeviceInfo &deviceInfo, QObject *parent = nullptr);
void reset(const DeviceInfo &newDeviceInfo);
const DeviceInfo &deviceInfo() const;
void setDeviceInfo(const DeviceInfo &newDeviceInfo);
@ -54,20 +52,25 @@ public:
//TODO: Replace with deviceInfo().name
const QString &name() const;
QSerialPort *serialPort() const;
signals:
void deviceInfoChanged();
void isPersistentChanged();
void isOnlineChanged();
void updateInfoChanged();
void statusChanged();
void errorChanged();
void statusStringChanged();
void isErrorChanged();
void progressChanged();
private slots:
void onDeviceInfoChanged();
void onIsOnlineChanged();
private:
DeviceInfo m_deviceInfo;
QSerialPort *m_serialPort;
bool m_isPersistent;
bool m_isOnline;

View File

@ -1,177 +0,0 @@
#include "firmwareupdater.h"
#include <QLoggingCategory>
#include "devicestate.h"
#include "recoveryinterface.h"
#include "utilityinterface.h"
#include "toplevel/wirelessstackupdateoperation.h"
#include "toplevel/firmwareinstalloperation.h"
#include "toplevel/settingsrestoreoperation.h"
#include "toplevel/settingsbackupoperation.h"
#include "toplevel/factoryresetoperation.h"
#include "toplevel/fullrepairoperation.h"
#include "toplevel/fullupdateoperation.h"
#include "preferences.h"
Q_LOGGING_CATEGORY(CATEGORY_TOPLEVEL, "TOPLEVEL")
using namespace Flipper;
using namespace Zero;
FirmwareUpdater::FirmwareUpdater(DeviceState *state, QObject *parent):
AbstractOperationRunner(parent),
m_state(state),
m_recovery(new RecoveryInterface(state, this)),
m_utility(new UtilityInterface(state, this))
{}
void FirmwareUpdater::fullUpdate(const Updates::VersionInfo &versionInfo)
{
if(!m_state->isRecoveryMode()) {
enqueueOperation(new FullUpdateOperation(m_recovery, m_utility, m_state, versionInfo, this));
}
}
void FirmwareUpdater::fullRepair(const Updates::VersionInfo &versionInfo)
{
if(m_state->isRecoveryMode()) {
enqueueOperation(new FullRepairOperation(m_recovery, m_utility, m_state, versionInfo, this));
}
}
void FirmwareUpdater::backupInternalStorage(const QUrl &directoryUrl)
{
if(!m_state->isRecoveryMode()) {
enqueueOperation(new SettingsBackupOperation(m_utility, m_state, directoryUrl, this));
}
}
void FirmwareUpdater::restoreInternalStorage(const QUrl &directoryUrl)
{
if(!m_state->isRecoveryMode()) {
enqueueOperation(new SettingsRestoreOperation(m_utility, m_state, directoryUrl, this));
}
}
void FirmwareUpdater::factoryReset()
{
if(!m_state->isRecoveryMode()) {
enqueueOperation(new FactoryResetOperation(m_utility, m_state, this));
}
}
void FirmwareUpdater::localFirmwareInstall(const QUrl &fileUrl)
{
enqueueOperation(new FirmwareInstallOperation(m_recovery, m_utility, m_state, fileUrl.toLocalFile(), this));
}
void FirmwareUpdater::localFUSUpdate(const QUrl &fileUrl)
{
//TODO: User-settable address
enqueueOperation(new FUSUpdateOperation(m_recovery, m_utility, m_state, fileUrl.toLocalFile(), 0x080EC000, this));
}
void FirmwareUpdater::localWirelessStackUpdate(const QUrl &fileUrl)
{
enqueueOperation(new WirelessStackUpdateOperation(m_recovery, m_utility, m_state, fileUrl.toLocalFile(), this));
}
const QLoggingCategory &FirmwareUpdater::loggingCategory() const
{
return CATEGORY_TOPLEVEL();
}
bool FirmwareUpdater::canUpdate(const Updates::VersionInfo &versionInfo) const
{
const auto &storageInfo = m_state->deviceInfo().storage;
if(storageInfo.isExternalPresent && !storageInfo.isAssetsInstalled) {
return true;
}
const auto &deviceChannel = branchToChannelName();
const auto &deviceVersion = (deviceChannel == channelName(ChannelType::Development)) ?
m_state->deviceInfo().firmware.commit :
m_state->deviceInfo().firmware.version;
const auto &deviceDate = m_state->deviceInfo().firmware.date;
const auto &serverChannel = globalPrefs->firmwareUpdateChannel();
const auto &serverVersion = versionInfo.number();
const auto &serverDate = versionInfo.date();
if(deviceChannel == channelName(ChannelType::Release)) {
if(serverChannel == channelName(ChannelType::Release)) {
return deviceVersion < serverVersion;
} else if(serverChannel == channelName(ChannelType::ReleaseCandidate)) {
return deviceVersion < serverVersion.chopped(serverVersion.length() - deviceVersion.length());
} else if(serverChannel == channelName(ChannelType::Development)) {
return deviceDate <= serverDate;
}
} else if(deviceChannel == channelName(ChannelType::ReleaseCandidate)) {
if(serverChannel == channelName(ChannelType::Release)) {
return deviceVersion.chopped(deviceVersion.length() - serverVersion.length()) <= serverVersion;
} else if(serverChannel == channelName(ChannelType::ReleaseCandidate)) {
return deviceVersion < serverVersion;
} else if(serverChannel == channelName(ChannelType::Development)) {
return deviceDate <= serverDate;
}
} else if(deviceChannel == channelName(ChannelType::Development)) {
if(serverChannel == channelName(ChannelType::Release)) {
return deviceDate <= serverDate;
} else if(serverChannel == channelName(ChannelType::ReleaseCandidate)) {
return deviceDate <= serverDate;
} else if(serverChannel == channelName(ChannelType::Development)) {
return (deviceVersion != serverVersion) && (deviceDate <= serverDate);
}
}
return false;
}
bool FirmwareUpdater::canInstall() const
{
const auto &deviceChannel = branchToChannelName();
const auto &serverChannel = globalPrefs->firmwareUpdateChannel();
return deviceChannel != serverChannel;
}
const QString &FirmwareUpdater::channelName(ChannelType channelType)
{
static const QStringList channelNames = {
QStringLiteral("development"), QStringLiteral("release-candidate"), QStringLiteral("release")
};
return channelNames[(int)channelType];
}
const QString &FirmwareUpdater::branchToChannelName() const
{
const auto &branchName = m_state->deviceInfo().firmware.branch;
if(branchName == QStringLiteral("dev")) {
return channelName(ChannelType::Development);
} else if(branchName.contains(QStringLiteral("-rc"))) {
return channelName(ChannelType::ReleaseCandidate);
} else {
return channelName(ChannelType::Release);
}
}
FirmwareUpdater::ChannelType FirmwareUpdater::branchToChannelType() const
{
const auto &branchName = m_state->deviceInfo().firmware.branch;
if(branchName == QStringLiteral("dev")) {
return ChannelType::Development;
} else if(branchName.contains(QStringLiteral("-rc"))) {
return ChannelType::ReleaseCandidate;
} else {
return ChannelType::Release;
}
}

View File

@ -1,55 +0,0 @@
#pragma once
#include "flipperupdates.h"
#include "abstractoperationrunner.h"
namespace Flipper {
namespace Zero {
class DeviceState;
class RecoveryInterface;
class UtilityInterface;
class FirmwareUpdater : public AbstractOperationRunner
{
Q_OBJECT
enum class ChannelType {
Development = 0,
ReleaseCandidate,
Release
};
public:
FirmwareUpdater(DeviceState *state, QObject *parent = nullptr);
public slots:
bool canUpdate(const Flipper::Updates::VersionInfo &versionInfo) const;
bool canInstall() const;
void fullUpdate(const Flipper::Updates::VersionInfo &versionInfo);
void fullRepair(const Flipper::Updates::VersionInfo &versionInfo);
void backupInternalStorage(const QUrl &directoryUrl);
void restoreInternalStorage(const QUrl &directoryUrl);
void factoryReset();
void localFirmwareInstall(const QUrl &fileUrl);
void localFUSUpdate(const QUrl &fileUrl);
void localWirelessStackUpdate(const QUrl &fileUrl);
private:
const QLoggingCategory &loggingCategory() const override;
static const QString &channelName(ChannelType channelType);
const QString &branchToChannelName() const;
ChannelType branchToChannelType() const;
DeviceState *m_state;
RecoveryInterface *m_recovery;
UtilityInterface *m_utility;
};
}
}

View File

@ -1,10 +1,28 @@
#include "flipperzero.h"
#include "preferences.h"
#include "flipperupdates.h"
#include "devicestate.h"
#include "firmwareupdater.h"
#include "screenstreamer.h"
#include "debug.h"
#include "commandinterface.h"
#include "utilityinterface.h"
#include "recoveryinterface.h"
#include "toplevel/wirelessstackupdateoperation.h"
#include "toplevel/firmwareinstalloperation.h"
#include "toplevel/settingsrestoreoperation.h"
#include "toplevel/settingsbackupoperation.h"
#include "toplevel/factoryresetoperation.h"
#include "toplevel/fullrepairoperation.h"
#include "toplevel/fullupdateoperation.h"
#include "preferences.h"
#define CHANNEL_DEVELOPMENT "development"
#define CHANNEL_RELEASE_CANDIDATE "release-candidate"
#define CHANNEL_RELEASE "release"
using namespace Flipper;
using namespace Zero;
@ -12,10 +30,16 @@ using namespace Zero;
FlipperZero::FlipperZero(const Zero::DeviceInfo &info, QObject *parent):
QObject(parent),
m_state(new DeviceState(info, this)),
m_updater(new FirmwareUpdater(m_state, this)),
m_streamer(new ScreenStreamer(m_state, this))
m_rpc(new CommandInterface(m_state, this)),
m_recovery(new RecoveryInterface(m_state, this)),
m_utility(new UtilityInterface(m_state, m_rpc, this)),
m_streamer(new ScreenStreamer(m_rpc, this))
{
connect(m_updater, &SignalingFailable::errorOccured, this, &FlipperZero::onErrorOccured);
connect(m_state, &DeviceState::isPersistentChanged, this, &FlipperZero::onStreamConditionChanged);
connect(m_state, &DeviceState::isOnlineChanged, this, &FlipperZero::onStreamConditionChanged);
// Add other connections as necessary.
connect(m_state, &DeviceState::deviceInfoChanged, this, &FlipperZero::stateChanged);
}
FlipperZero::~FlipperZero()
@ -23,6 +47,117 @@ FlipperZero::~FlipperZero()
m_state->setOnline(false);
}
bool FlipperZero::canUpdate(const Updates::VersionInfo &versionInfo) const
{
const auto &storageInfo = m_state->deviceInfo().storage;
if(storageInfo.isExternalPresent && !storageInfo.isAssetsInstalled) {
return true;
}
static const auto DEVELOPMENT = QStringLiteral("development");
static const auto RELEASE_CANDIDATE = QStringLiteral("release-candidate");
static const auto RELEASE = QStringLiteral("release");
const auto &firmwareInfo = m_state->deviceInfo().firmware;
const auto &deviceChannel = firmwareInfo.channel;
const auto &deviceVersion = (deviceChannel == QStringLiteral("development")) ?
firmwareInfo.commit :
firmwareInfo.version;
const auto &deviceDate = firmwareInfo.date;
const auto &serverChannel = globalPrefs->firmwareUpdateChannel();
const auto &serverVersion = versionInfo.number();
const auto &serverDate = versionInfo.date();
if(deviceChannel == RELEASE) {
if(serverChannel == RELEASE) {
return deviceVersion < serverVersion;
} else if(serverChannel == RELEASE_CANDIDATE) {
return deviceVersion < serverVersion.chopped(serverVersion.length() - deviceVersion.length());
} else if(serverChannel == DEVELOPMENT) {
return deviceDate <= serverDate;
}
} else if(deviceChannel == RELEASE_CANDIDATE) {
if(serverChannel == RELEASE) {
return deviceVersion.chopped(deviceVersion.length() - serverVersion.length()) <= serverVersion;
} else if(serverChannel == RELEASE_CANDIDATE) {
return deviceVersion < serverVersion;
} else if(serverChannel == DEVELOPMENT) {
return deviceDate <= serverDate;
}
} else if(deviceChannel == DEVELOPMENT) {
if(serverChannel == RELEASE) {
return deviceDate <= serverDate;
} else if(serverChannel == RELEASE_CANDIDATE) {
return deviceDate <= serverDate;
} else if(serverChannel == DEVELOPMENT) {
return (deviceVersion != serverVersion) && (deviceDate <= serverDate);
}
}
return false;
}
bool FlipperZero::canInstall(const Updates::VersionInfo &versionInfo) const
{
Q_UNUSED(versionInfo)
const auto &deviceChannel = m_state->deviceInfo().firmware.channel;
const auto &serverChannel = globalPrefs->firmwareUpdateChannel();
return deviceChannel != serverChannel;
}
bool FlipperZero::canRepair(const Updates::VersionInfo &versionInfo) const
{
Q_UNUSED(versionInfo)
return m_state->isRecoveryMode();
}
void FlipperZero::fullUpdate(const Updates::VersionInfo &versionInfo)
{
registerOperation(new FullUpdateOperation(m_recovery, m_utility, m_state, versionInfo, this));
}
void FlipperZero::fullRepair(const Updates::VersionInfo &versionInfo)
{
registerOperation(new FullRepairOperation(m_recovery, m_utility, m_state, versionInfo, this));
}
void FlipperZero::createBackup(const QUrl &directoryUrl)
{
registerOperation(new SettingsBackupOperation(m_utility, m_state, directoryUrl, this));
}
void FlipperZero::restoreBackup(const QUrl &directoryUrl)
{
registerOperation(new SettingsRestoreOperation(m_utility, m_state, directoryUrl, this));
}
void FlipperZero::factoryReset()
{
registerOperation(new FactoryResetOperation(m_utility, m_state, this));
}
void FlipperZero::installFirmware(const QUrl &fileUrl)
{
registerOperation(new FirmwareInstallOperation(m_recovery, m_utility, m_state, fileUrl.toLocalFile(), this));
}
void FlipperZero::installWirelessStack(const QUrl &fileUrl)
{
registerOperation(new WirelessStackUpdateOperation(m_recovery, m_utility, m_state, fileUrl.toLocalFile(), this));
}
void FlipperZero::installFUS(const QUrl &fileUrl, uint32_t address)
{
registerOperation(new FUSUpdateOperation(m_recovery, m_utility, m_state, fileUrl.toLocalFile(), address, this));
}
DeviceState *FlipperZero::deviceState() const
{
return m_state;
@ -33,13 +168,41 @@ Flipper::Zero::ScreenStreamer *FlipperZero::streamer() const
return m_streamer;
}
FirmwareUpdater *FlipperZero::updater() const
void FlipperZero::onStreamConditionChanged()
{
return m_updater;
// Automatically start screen streaming if the conditions are right:
// 1. There is no error
// 2. Device is online and connected in VCP mode
// 3. There is no ongoing operation
const auto streamCondition = m_state->isOnline() &&
!(m_state->isError() || m_state->isRecoveryMode() || m_state->isPersistent());
if(streamCondition) {
m_streamer->start();
}
}
void FlipperZero::onErrorOccured()
void FlipperZero::registerOperation(AbstractOperation *operation)
{
auto *instance = qobject_cast<SignalingFailable*>(sender());
m_state->setErrorString(instance->errorString());
connect(operation, &AbstractOperation::finished, this, [=]() {
if(operation->isError()) {
m_state->setErrorString(operation->errorString());
}
operation->deleteLater();
emit operationFinished();
});
if(m_state->isRecoveryMode()) {
operation->start();
} else {
connect(m_streamer, &ScreenStreamer::stopped, operation, [=]() {
//TODO: Check that ScreenStreamer has correctly stopped
operation->start();
});
m_streamer->stop();
}
}

View File

@ -2,37 +2,64 @@
#include <QObject>
#include "deviceinfo.h"
class AbstractOperation;
namespace Flipper {
namespace Updates {
class VersionInfo;
}
namespace Zero {
struct DeviceInfo;
class DeviceState;
class CommandInterface;
class RecoveryInterface;
class UtilityInterface;
class ScreenStreamer;
class FirmwareUpdater;
}
class FlipperZero : public QObject
{
Q_OBJECT
Q_PROPERTY(Flipper::Zero::DeviceState* state READ deviceState CONSTANT)
Q_PROPERTY(Flipper::Zero::FirmwareUpdater* updater READ updater CONSTANT)
Q_PROPERTY(Flipper::Zero::ScreenStreamer* streamer READ streamer CONSTANT)
public:
FlipperZero(const Zero::DeviceInfo &info, QObject *parent = nullptr);
~FlipperZero();
bool canUpdate(const Flipper::Updates::VersionInfo &versionInfo) const;
bool canInstall(const Flipper::Updates::VersionInfo &versionInfo) const;
bool canRepair(const Flipper::Updates::VersionInfo &versionInfo) const;
void fullUpdate(const Flipper::Updates::VersionInfo &versionInfo);
void fullRepair(const Flipper::Updates::VersionInfo &versionInfo);
void createBackup(const QUrl &directoryUrl);
void restoreBackup(const QUrl &directoryUrl);
void factoryReset();
void installFirmware(const QUrl &fileUrl);
void installWirelessStack(const QUrl &fileUrl);
void installFUS(const QUrl &fileUrl, uint32_t address);
Flipper::Zero::DeviceState *deviceState() const;
Flipper::Zero::ScreenStreamer *streamer() const;
Flipper::Zero::FirmwareUpdater *updater() const;
signals:
void stateChanged();
void operationFinished();
private slots:
void onErrorOccured();
void onStreamConditionChanged();
private:
void registerOperation(AbstractOperation *operation);
Zero::DeviceState *m_state;
Zero::FirmwareUpdater *m_updater;
Zero::CommandInterface *m_rpc;
Zero::RecoveryInterface *m_recovery;
Zero::UtilityInterface *m_utility;
Zero::ScreenStreamer *m_streamer;
};

View File

@ -2,18 +2,22 @@
#include <cmath>
#include <QDebug>
#include <QTimer>
#include <QSerialPort>
#include "flipperzero/cli/deviceinfooperation.h"
#include "flipperzero/cli/skipmotdoperation.h"
#include "flipperzero/cli/statoperation.h"
#include "flipperzero/factoryinfo.h"
#include "flipperzero/cli/stoprpcoperation.h"
#include "flipperzero/cli/storagestatoperation.h"
#include "flipperzero/cli/storageinfooperation.h"
#include "flipperzero/cli/systemdeviceinfooperation.h"
#include "device/stm32wb55.h"
#include "serialinithelper.h"
#include "serialfinder.h"
#include "debug.h"
using namespace Flipper;
using namespace Zero;
@ -34,10 +38,14 @@ AbstractDeviceInfoHelper *AbstractDeviceInfoHelper::create(const USBDeviceInfo &
} else if(pid == 0xdf11) {
return new DFUDeviceInfoHelper(info, parent);
} else {
error_msg("Not a Flipper Zero device.")
qCritical() << "Not a Flipper Zero device";
return nullptr;
}
}
return nullptr;
const DeviceInfo &AbstractDeviceInfoHelper::result() const
{
return m_deviceInfo;
}
VCPDeviceInfoHelper::VCPDeviceInfoHelper(const USBDeviceInfo &info, QObject *parent):
@ -47,11 +55,6 @@ VCPDeviceInfoHelper::VCPDeviceInfoHelper(const USBDeviceInfo &info, QObject *par
m_deviceInfo.usbInfo = info;
}
const DeviceInfo &VCPDeviceInfoHelper::result() const
{
return m_deviceInfo;
}
void VCPDeviceInfoHelper::nextStateLogic()
{
if(state() == AbstractDeviceInfoHelper::Ready) {
@ -59,10 +62,10 @@ void VCPDeviceInfoHelper::nextStateLogic()
findSerialPort();
} else if(state() == VCPDeviceInfoHelper::FindingSerialPort) {
setState(VCPDeviceInfoHelper::SkippingMOTD);
skipMOTD();
setState(VCPDeviceInfoHelper::InitializingSerialPort);
initSerialPort();
} else if(state() == VCPDeviceInfoHelper::SkippingMOTD) {
} else if(state() == VCPDeviceInfoHelper::InitializingSerialPort) {
setState(VCPDeviceInfoHelper::FetchingDeviceInfo);
fetchDeviceInfo();
@ -75,6 +78,10 @@ void VCPDeviceInfoHelper::nextStateLogic()
checkManifest();
} else if(state() == VCPDeviceInfoHelper::CheckingManifest) {
setState(VCPDeviceInfoHelper::StoppingRPCSession);
stopRPCSession();
} else if(state() == VCPDeviceInfoHelper::StoppingRPCSession) {
closePortAndFinish();
}
}
@ -88,58 +95,81 @@ void VCPDeviceInfoHelper::findSerialPort()
finishWithError(QStringLiteral("Invalid serial port info."));
} else {
m_deviceInfo.serialInfo = portInfo;
m_deviceInfo.portInfo = portInfo;
m_deviceInfo.systemLocation = portInfo.systemLocation();
m_serialPort = new QSerialPort(portInfo, this);
if(!m_serialPort->open(QIODevice::ReadWrite)) {
finishWithError(m_serialPort->errorString());
} else {
advanceState();
}
advanceState();
}
});
}
void VCPDeviceInfoHelper::skipMOTD()
void VCPDeviceInfoHelper::initSerialPort()
{
auto *operation = new SkipMOTDOperation(m_serialPort, this);
auto *helper = new SerialInitHelper(m_deviceInfo.portInfo, this);
connect(operation, &AbstractOperation::finished, this, [=]() {
if(operation->isError()) {
finishWithError(operation->errorString());
connect(helper, &AbstractOperationHelper::finished, this, [=]() {
if(helper->isError()) {
finishWithError(helper->errorString());
} else {
m_serialPort = helper->serialPort();
advanceState();
}
});
operation->start();
}
void VCPDeviceInfoHelper::fetchDeviceInfo()
{
auto *operation = new DeviceInfoOperation(m_serialPort, this);
auto *operation = new SystemDeviceInfoOperation(m_serialPort, this);
connect(operation, &AbstractOperation::finished, this, [=]() {
if(operation->isError()) {
finishWithError(operation->errorString());
} else {
// TODO: Is there a better way to do this?
const auto &info = operation->result();
m_deviceInfo.name = info.name;
m_deviceInfo.bootloader = info.bootloader;
m_deviceInfo.firmware = info.firmware;
m_deviceInfo.hardware = info.hardware;
m_deviceInfo.fusVersion = info.fusVersion;
m_deviceInfo.radioVersion = info.radioVersion;
if(m_deviceInfo.name.isEmpty()) {
finishWithError(QStringLiteral("Failed to read device factory information"));
} else {
advanceState();
}
return;
}
m_deviceInfo.name = operation->result(QByteArrayLiteral("hardware_name"));
m_deviceInfo.bootloader = {
operation->result(QByteArrayLiteral("bootloader_version")),
operation->result(QByteArrayLiteral("bootloader_commit")),
operation->result(QByteArrayLiteral("bootloader_branch")),
branchToChannelName(operation->result(QByteArrayLiteral("bootloader_branch"))),
QDateTime::fromString(operation->result(QByteArrayLiteral("bootloader_build_date")), "dd-MM-yyyy").date()
};
m_deviceInfo.firmware = {
operation->result(QByteArrayLiteral("firmware_version")),
operation->result(QByteArrayLiteral("firmware_commit")),
operation->result(QByteArrayLiteral("firmware_branch")),
branchToChannelName(operation->result(QByteArrayLiteral("firmware_branch"))),
QDateTime::fromString(operation->result(QByteArrayLiteral("firmware_build_date")), "dd-MM-yyyy").date()
};
m_deviceInfo.hardware = {
operation->result(QByteArrayLiteral("hardware_ver")),
QByteArrayLiteral("f") + operation->result(QByteArrayLiteral("hardware_target")),
QByteArrayLiteral("b") + operation->result(QByteArrayLiteral("hardware_body")),
QByteArrayLiteral("c") + operation->result(QByteArrayLiteral("hardware_connect")),
(HardwareInfo::Color)operation->result(QByteArrayLiteral("hardware_color")).toInt(),
};
m_deviceInfo.fusVersion = QStringLiteral("%1.%2.%3").arg(
operation->result(QByteArrayLiteral("radio_fus_major")),
operation->result(QByteArrayLiteral("radio_fus_minor")),
operation->result(QByteArrayLiteral("radio_fus_sub")));
m_deviceInfo.radioVersion = QStringLiteral("%1.%2.%3").arg(
operation->result(QByteArrayLiteral("radio_stack_major")),
operation->result(QByteArrayLiteral("radio_stack_minor")),
operation->result(QByteArrayLiteral("radio_stack_sub")));
if(m_deviceInfo.name.isEmpty()) {
finishWithError(QStringLiteral("Failed to read device information"));
} else {
advanceState();
}
operation->deleteLater();
});
operation->start();
@ -147,22 +177,27 @@ void VCPDeviceInfoHelper::fetchDeviceInfo()
void VCPDeviceInfoHelper::checkSDCard()
{
auto *operation = new StatOperation(m_serialPort, QByteArrayLiteral("/ext"), this);
auto *operation = new StorageInfoOperation(m_serialPort, QByteArrayLiteral("/ext"), this);
connect(operation, &AbstractOperation::finished, this, [=]() {
if(operation->isError()) {
finishWithError(operation->errorString());
} else if(operation->type() != StatOperation::Type::Storage) {
} else if(!operation->isPresent()) {
m_deviceInfo.storage.isExternalPresent = false;
m_deviceInfo.storage.isAssetsInstalled = false;
closePortAndFinish();
setState(VCPDeviceInfoHelper::CheckingManifest);
advanceState();
} else {
m_deviceInfo.storage.isExternalPresent = true;
m_deviceInfo.storage.externalFree = floor((double)operation->sizeFree() * 100.0 / (double)operation->size());
m_deviceInfo.storage.externalFree = floor((double)operation->sizeFree() * 100.0 /
(double)operation->sizeTotal());
advanceState();
}
operation->deleteLater();
});
operation->start();
@ -170,16 +205,34 @@ void VCPDeviceInfoHelper::checkSDCard()
void VCPDeviceInfoHelper::checkManifest()
{
auto *operation = new StatOperation(m_serialPort, QByteArrayLiteral("/ext/Manifest"), this);
auto *operation = new StorageStatOperation(m_serialPort, QByteArrayLiteral("/ext/Manifest"), this);
connect(operation, &AbstractOperation::finished, this, [=]() {
if(operation->isError()) {
finishWithError(operation->errorString());
} else {
m_deviceInfo.storage.isAssetsInstalled = (operation->type() == StatOperation::Type::RegularFile);
m_deviceInfo.storage.isAssetsInstalled = operation->isPresent() && (operation->type() == StorageStatOperation::Type::RegularFile);
advanceState();
}
operation->deleteLater();
});
operation->start();
}
void VCPDeviceInfoHelper::stopRPCSession()
{
auto *operation = new StopRPCOperation(m_serialPort, this);
connect(operation, &AbstractOperation::finished, this, [=]() {
if(operation->isError()) {
finishWithError(operation->errorString());
} else {
advanceState();
}
operation->deleteLater();
});
operation->start();
@ -191,6 +244,21 @@ void VCPDeviceInfoHelper::closePortAndFinish()
finish();
}
const QString &VCPDeviceInfoHelper::branchToChannelName(const QByteArray &branchName)
{
static const auto DEVELOPMENT = QStringLiteral("development");
static const auto RELEASE_CANDIDATE = QStringLiteral("release-candidate");
static const auto RELEASE = QStringLiteral("release");
if(branchName == QByteArrayLiteral("dev")) {
return DEVELOPMENT;
} else if(branchName.contains(QByteArrayLiteral("-rc"))) {
return RELEASE_CANDIDATE;
} else {
return RELEASE;
}
}
using namespace STM32;
DFUDeviceInfoHelper::DFUDeviceInfoHelper(const USBDeviceInfo &info, QObject *parent):
@ -202,11 +270,6 @@ DFUDeviceInfoHelper::DFUDeviceInfoHelper(const USBDeviceInfo &info, QObject *par
m_deviceInfo.storage.isAssetsInstalled = false;
}
const DeviceInfo &DFUDeviceInfoHelper::result() const
{
return m_deviceInfo;
}
void DFUDeviceInfoHelper::nextStateLogic()
{
STM32WB55 device(m_deviceInfo.usbInfo);

View File

@ -21,7 +21,10 @@ public:
virtual ~AbstractDeviceInfoHelper();
static AbstractDeviceInfoHelper *create(const USBDeviceInfo &info, QObject *parent = nullptr);
virtual const DeviceInfo &result() const = 0;
const DeviceInfo &result() const;
protected:
DeviceInfo m_deviceInfo;
};
class VCPDeviceInfoHelper : public AbstractDeviceInfoHelper
@ -30,28 +33,31 @@ class VCPDeviceInfoHelper : public AbstractDeviceInfoHelper
enum OperationState {
FindingSerialPort = AbstractOperationHelper::User,
SkippingMOTD,
InitializingSerialPort,
FetchingDeviceInfo,
CheckingSDCard,
CheckingManifest
CheckingManifest,
StoppingRPCSession
};
public:
VCPDeviceInfoHelper(const USBDeviceInfo &info, QObject *parent = nullptr);
const DeviceInfo &result() const override;
private:
void nextStateLogic() override;
void findSerialPort();
void skipMOTD();
void initSerialPort();
void fetchDeviceInfo();
void checkSDCard();
void checkManifest();
void stopRPCSession();
void closePortAndFinish();
private:
static const QString &branchToChannelName(const QByteArray &branchName);
QSerialPort *m_serialPort;
DeviceInfo m_deviceInfo;
};
class DFUDeviceInfoHelper : public AbstractDeviceInfoHelper
@ -60,12 +66,9 @@ class DFUDeviceInfoHelper : public AbstractDeviceInfoHelper
public:
DFUDeviceInfoHelper(const USBDeviceInfo &info, QObject *parent = nullptr);
const DeviceInfo &result() const override;
private:
void nextStateLogic() override;
DeviceInfo m_deviceInfo;
};
}

View File

@ -0,0 +1,80 @@
#include "serialinithelper.h"
#include <QSerialPort>
#include "flipperzero/cli/skipmotdoperation.h"
#include "flipperzero/cli/startrpcoperation.h"
using namespace Flipper;
using namespace Zero;
SerialInitHelper::SerialInitHelper(const QSerialPortInfo &portInfo, QObject *parent):
AbstractOperationHelper(parent),
m_serialPort(new QSerialPort(portInfo, parent))
{}
QSerialPort *SerialInitHelper::serialPort() const
{
return m_serialPort;
}
void SerialInitHelper::nextStateLogic()
{
if(state() == AbstractOperationHelper::Ready) {
setState(SerialInitHelper::OpeningPort);
openPort();
} else if(state() == SerialInitHelper::OpeningPort) {
setState(SerialInitHelper::SkippingMOTD);
skipMOTD();
} else if(state() == SerialInitHelper::SkippingMOTD) {
setState(SerialInitHelper::StartingRPCSession);
startRPCSession();
} else if(state() == SerialInitHelper::StartingRPCSession) {
finish();
}
}
void SerialInitHelper::openPort()
{
if(!m_serialPort->open(QIODevice::ReadWrite)) {
finishWithError(QStringLiteral("Failed to open serial port: %1").arg(m_serialPort->errorString()));
} else {
advanceState();
}
}
void SerialInitHelper::skipMOTD()
{
auto *operation = new SkipMOTDOperation(m_serialPort, this);
connect(operation, &AbstractOperation::finished, this, [=]() {
if(operation->isError()) {
finishWithError(operation->errorString());
} else {
advanceState();
}
operation->deleteLater();
});
operation->start();
}
void SerialInitHelper::startRPCSession()
{
auto *operation = new StartRPCOperation(m_serialPort, this);
connect(operation, &AbstractOperation::finished, this, [=]() {
if(operation->isError()) {
finishWithError(operation->errorString());
} else {
advanceState();
}
operation->deleteLater();
});
operation->start();
}

View File

@ -0,0 +1,36 @@
#pragma once
#include "abstractoperationhelper.h"
#include <QSerialPortInfo>
namespace Flipper {
namespace Zero {
class SerialInitHelper : public AbstractOperationHelper
{
Q_OBJECT
enum State {
OpeningPort = AbstractOperationHelper::User,
SkippingMOTD,
StartingRPCSession
};
public:
SerialInitHelper(const QSerialPortInfo &portInfo, QObject *parent = nullptr);
QSerialPort *serialPort() const;
private:
void nextStateLogic() override;
void openPort();
void skipMOTD();
void startRPCSession();
QSerialPort *m_serialPort;
};
}
}

View File

@ -0,0 +1,81 @@
#include "toplevelhelper.h"
#include "updateregistry.h"
#include "flipperzero/flipperzero.h"
#include "flipperzero/devicestate.h"
using namespace Flipper;
using namespace Zero;
AbstractTopLevelHelper::AbstractTopLevelHelper(UpdateRegistry *updateRegistry, FlipperZero *device, QObject *parent):
AbstractOperationHelper(parent),
m_updateRegistry(updateRegistry),
m_device(device)
{
connect(m_device, &FlipperZero::operationFinished, this, &AbstractTopLevelHelper::finish);
}
UpdateRegistry *AbstractTopLevelHelper::updateRegistry()
{
return m_updateRegistry;
}
FlipperZero *AbstractTopLevelHelper::device()
{
return m_device;
}
void AbstractTopLevelHelper::onUpdatesChecked()
{
disconnect(m_updateRegistry, &UpdateRegistry::channelsChanged, this, &AbstractTopLevelHelper::onUpdatesChecked);
if(!m_updateRegistry->isReady()) {
finishWithError(QStringLiteral("Failed to retreive update information"));
} else{
advanceState();
}
}
void AbstractTopLevelHelper::nextStateLogic()
{
if(state() == AbstractOperationHelper::Ready) {
setState(AbstractTopLevelHelper::CheckingForUpdates);
checkForUpdates();
} else if(state() == AbstractTopLevelHelper::CheckingForUpdates) {
setState(AbstractTopLevelHelper::RunningCustomOperation);
runCustomOperation();
} else if(state() == AbstractTopLevelHelper::RunningCustomOperation) {
finish();
}
}
void AbstractTopLevelHelper::checkForUpdates()
{
m_device->deviceState()->setStatusString(tr("Checking for updates..."));
connect(m_updateRegistry, &UpdateRegistry::channelsChanged, this, &AbstractTopLevelHelper::onUpdatesChecked);
m_updateRegistry->check();
}
UpdateTopLevelHelper::UpdateTopLevelHelper(UpdateRegistry *updateRegistry, FlipperZero *device, QObject *parent):
AbstractTopLevelHelper(updateRegistry, device, parent)
{}
void UpdateTopLevelHelper::runCustomOperation()
{
auto &versionInfo = updateRegistry()->latestVersion();
device()->fullUpdate(versionInfo);
}
RepairTopLevelHelper::RepairTopLevelHelper(UpdateRegistry *updateRegistry, FlipperZero *device, QObject *parent):
AbstractTopLevelHelper(updateRegistry, device, parent)
{}
void RepairTopLevelHelper::runCustomOperation()
{
auto &versionInfo = updateRegistry()->latestVersion();
device()->fullRepair(versionInfo);
}

View File

@ -0,0 +1,67 @@
#pragma once
#include "abstractoperationhelper.h"
#include "flipperupdates.h"
namespace Flipper {
class UpdateRegistry;
class FlipperZero;
namespace Zero {
class AbstractTopLevelHelper : public AbstractOperationHelper
{
Q_OBJECT
public:
enum State {
CheckingForUpdates = AbstractOperationHelper::User,
RunningCustomOperation,
};
AbstractTopLevelHelper(UpdateRegistry *updateRegistry, FlipperZero *device, QObject *parent = nullptr);
virtual ~AbstractTopLevelHelper() {}
protected:
UpdateRegistry *updateRegistry();
FlipperZero *device();
private slots:
void onUpdatesChecked();
private:
void nextStateLogic() override;
void checkForUpdates();
virtual void runCustomOperation() = 0;
UpdateRegistry *m_updateRegistry;
FlipperZero *m_device;
};
class UpdateTopLevelHelper : public AbstractTopLevelHelper
{
Q_OBJECT
public:
UpdateTopLevelHelper(UpdateRegistry *updateRegistry, FlipperZero *device, QObject *parent = nullptr);
private:
void runCustomOperation() override;
};
class RepairTopLevelHelper : public AbstractTopLevelHelper
{
Q_OBJECT
public:
RepairTopLevelHelper(UpdateRegistry *updateRegistry, FlipperZero *device, QObject *parent = nullptr);
private:
void runCustomOperation() override;
};
}
}

View File

@ -0,0 +1,33 @@
#include "guiprotobufmessage.h"
using namespace Flipper;
using namespace Zero;
GuiStartScreenStreamRequest::GuiStartScreenStreamRequest(QSerialPort *serialPort):
AbstractMainProtobufRequest(serialPort)
{
pbMessage()->content.gui_start_screen_stream_request = PB_Gui_StartScreenStreamRequest_init_default;
}
GuiStopScreenStreamRequest::GuiStopScreenStreamRequest(QSerialPort *serialPort):
AbstractMainProtobufRequest(serialPort)
{
pbMessage()->content.gui_start_screen_stream_request = PB_Gui_StopScreenStreamRequest_init_default;
}
GuiScreenFrameResponse::GuiScreenFrameResponse(QSerialPort *serialPort):
AbstractMainProtobufResponse(serialPort)
{}
const QByteArray GuiScreenFrameResponse::screenFrame() const
{
const auto *data = pbMessage()->content.gui_screen_frame.data;
return QByteArray((const char*)data->bytes, data->size);
}
GuiSendInputRequest::GuiSendInputRequest(QSerialPort *serialPort, PB_Gui_InputKey key, PB_Gui_InputType type):
AbstractMainProtobufRequest(serialPort)
{
pbMessage()->content.gui_send_input_event_request.key = key;
pbMessage()->content.gui_send_input_event_request.type = type;
}

View File

@ -0,0 +1,39 @@
#pragma once
#include "mainprotobufmessage.h"
namespace Flipper {
namespace Zero {
class GuiStartScreenStreamRequest:
public AbstractMainProtobufRequest<PB_Main_gui_start_screen_stream_request_tag>
{
public:
GuiStartScreenStreamRequest(QSerialPort *serialPort);
};
class GuiStopScreenStreamRequest:
public AbstractMainProtobufRequest<PB_Main_gui_stop_screen_stream_request_tag>
{
public:
GuiStopScreenStreamRequest(QSerialPort *serialPort);
};
class GuiScreenFrameResponse:
public AbstractMainProtobufResponse<PB_Main_gui_screen_frame_tag>
{
public:
GuiScreenFrameResponse(QSerialPort *serialPort);
const QByteArray screenFrame() const;
};
class GuiSendInputRequest:
public AbstractMainProtobufRequest<PB_Main_gui_send_input_event_request_tag>
{
public:
GuiSendInputRequest(QSerialPort *serialPort, PB_Gui_InputKey key, PB_Gui_InputType type);
};
}
}

View File

@ -0,0 +1,14 @@
#include "mainprotobufmessage.h"
using namespace Flipper;
using namespace Zero;
MainEmptyResponse::MainEmptyResponse(QSerialPort *serialPort):
AbstractMainProtobufResponse(serialPort)
{}
MainStopSessionRequest::MainStopSessionRequest(QSerialPort *serialPort):
AbstractMainProtobufRequest(serialPort)
{
pbMessage()->content.stop_session = PB_StopSession_init_default;
}

View File

@ -0,0 +1,132 @@
#pragma once
#include <QHash>
#include "abstractprotobufmessage.h"
#include "messages/flipper.pb.h"
namespace Flipper {
namespace Zero {
template<const pb_size_t Tag>
class AbstractMainProtobufRequest : public AbstractProtobufRequest<&PB_Main_msg, PB_Main>
{
public:
AbstractMainProtobufRequest(QSerialPort *serialPort, bool hasNext = false);
};
template<const pb_size_t Tag>
class AbstractMainProtobufResponse : public AbstractProtobufResponse<&PB_Main_msg, PB_Main>
{
public:
AbstractMainProtobufResponse(QSerialPort *serialPort):
AbstractProtobufResponse<&PB_Main_msg, PB_Main>(serialPort)
{}
virtual ~AbstractMainProtobufResponse() {}
bool hasNext() const;
PB_CommandStatus commandStatus() const;
const QString commandStatusString() const;
quint32 whichContent() const;
bool isOk() const;
bool isValidType() const;
static pb_size_t tag();
};
template<const pb_size_t Tag>
AbstractMainProtobufRequest<Tag>::AbstractMainProtobufRequest(QSerialPort *serialPort, bool hasNext):
AbstractProtobufRequest<&PB_Main_msg, PB_Main>(serialPort)
{
pbMessage()->which_content = Tag;
pbMessage()->has_next = hasNext;
}
template<const pb_size_t Tag>
bool AbstractMainProtobufResponse<Tag>::hasNext() const
{
return pbMessage()->has_next;
}
template<const pb_size_t Tag>
PB_CommandStatus AbstractMainProtobufResponse<Tag>::commandStatus() const
{
return pbMessage()->command_status;
}
template<const pb_size_t Tag>
const QString AbstractMainProtobufResponse<Tag>::commandStatusString() const
{
static const QHash<PB_CommandStatus, QString> statusStrings = {
{PB_CommandStatus_OK, QStringLiteral("No error")},
// Common errors
{PB_CommandStatus_ERROR, QStringLiteral("Unknown")},
{PB_CommandStatus_ERROR_DECODE, QStringLiteral("Decode failure")},
{PB_CommandStatus_ERROR_NOT_IMPLEMENTED, QStringLiteral("Commant not implemented")},
{PB_CommandStatus_ERROR_BUSY, QStringLiteral("Device is busy")},
{PB_CommandStatus_ERROR_CONTINUOUS_COMMAND_INTERRUPTED, QStringLiteral("Continuous command interrupted")},
{PB_CommandStatus_ERROR_INVALID_PARAMETERS, QStringLiteral("Invalid parameters")},
// Storage errors
{PB_CommandStatus_ERROR_STORAGE_NOT_READY, QStringLiteral("Storage not ready")},
{PB_CommandStatus_ERROR_STORAGE_EXIST, QStringLiteral("File/directory already exists")},
{PB_CommandStatus_ERROR_STORAGE_NOT_EXIST, QStringLiteral("File/directory does not exist")},
{PB_CommandStatus_ERROR_STORAGE_INVALID_PARAMETER, QStringLiteral("Invalid storage API parameter")},
{PB_CommandStatus_ERROR_STORAGE_DENIED, QStringLiteral("Access denied")},
{PB_CommandStatus_ERROR_STORAGE_INVALID_NAME, QStringLiteral("Invalid name/path")},
{PB_CommandStatus_ERROR_STORAGE_INTERNAL, QStringLiteral("Internal error")},
{PB_CommandStatus_ERROR_STORAGE_NOT_IMPLEMENTED, QStringLiteral("Storage command not implemented")},
{PB_CommandStatus_ERROR_STORAGE_ALREADY_OPEN, QStringLiteral("File/directory is already open")},
{PB_CommandStatus_ERROR_STORAGE_DIR_NOT_EMPTY, QStringLiteral("Directory is not empty")},
// Application errors
{PB_CommandStatus_ERROR_APP_CANT_START, QStringLiteral("Cannot start the application")},
{PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED, QStringLiteral("Another application is already running")},
// Virtual display errors
{PB_CommandStatus_ERROR_VIRTUAL_DISPLAY_ALREADY_STARTED, QStringLiteral("Virtual display session has already been started")},
{PB_CommandStatus_ERROR_VIRTUAL_DISPLAY_NOT_STARTED, QStringLiteral("No virtual display session running")}
};
return statusStrings[commandStatus()];
}
template<const pb_size_t Tag>
quint32 AbstractMainProtobufResponse<Tag>::whichContent() const
{
return pbMessage()->which_content;
}
template<const pb_size_t Tag>
bool AbstractMainProtobufResponse<Tag>::isOk() const
{
return commandStatus() == PB_CommandStatus_OK;
}
template<const pb_size_t Tag>
bool AbstractMainProtobufResponse<Tag>::isValidType() const
{
return pbMessage()->which_content == Tag;
}
template<const pb_size_t Tag>
pb_size_t AbstractMainProtobufResponse<Tag>::tag()
{
return Tag;
}
class MainStopSessionRequest : public AbstractMainProtobufRequest<PB_Main_stop_session_tag>
{
public:
MainStopSessionRequest(QSerialPort *serialPort);
};
class MainEmptyResponse : public AbstractMainProtobufResponse<PB_Main_empty_tag>
{
public:
MainEmptyResponse(QSerialPort *serialPort);
};
}
}

View File

@ -0,0 +1,18 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.6-dev */
#include "application.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
PB_BIND(PB_App_StartRequest, PB_App_StartRequest, AUTO)
PB_BIND(PB_App_LockStatusRequest, PB_App_LockStatusRequest, AUTO)
PB_BIND(PB_App_LockStatusResponse, PB_App_LockStatusResponse, AUTO)

View File

@ -0,0 +1,79 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.6-dev */
#ifndef PB_PB_APP_APPLICATION_PB_H_INCLUDED
#define PB_PB_APP_APPLICATION_PB_H_INCLUDED
#include <pb.h>
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
/* Struct definitions */
typedef struct _PB_App_LockStatusRequest {
char dummy_field;
} PB_App_LockStatusRequest;
typedef struct _PB_App_StartRequest {
char *name;
char *args;
} PB_App_StartRequest;
typedef struct _PB_App_LockStatusResponse {
bool locked;
} PB_App_LockStatusResponse;
#ifdef __cplusplus
extern "C" {
#endif
/* Initializer values for message structs */
#define PB_App_StartRequest_init_default {NULL, NULL}
#define PB_App_LockStatusRequest_init_default {0}
#define PB_App_LockStatusResponse_init_default {0}
#define PB_App_StartRequest_init_zero {NULL, NULL}
#define PB_App_LockStatusRequest_init_zero {0}
#define PB_App_LockStatusResponse_init_zero {0}
/* Field tags (for use in manual encoding/decoding) */
#define PB_App_StartRequest_name_tag 1
#define PB_App_StartRequest_args_tag 2
#define PB_App_LockStatusResponse_locked_tag 1
/* Struct field encoding specification for nanopb */
#define PB_App_StartRequest_FIELDLIST(X, a) \
X(a, POINTER, SINGULAR, STRING, name, 1) \
X(a, POINTER, SINGULAR, STRING, args, 2)
#define PB_App_StartRequest_CALLBACK NULL
#define PB_App_StartRequest_DEFAULT NULL
#define PB_App_LockStatusRequest_FIELDLIST(X, a) \
#define PB_App_LockStatusRequest_CALLBACK NULL
#define PB_App_LockStatusRequest_DEFAULT NULL
#define PB_App_LockStatusResponse_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, locked, 1)
#define PB_App_LockStatusResponse_CALLBACK NULL
#define PB_App_LockStatusResponse_DEFAULT NULL
extern const pb_msgdesc_t PB_App_StartRequest_msg;
extern const pb_msgdesc_t PB_App_LockStatusRequest_msg;
extern const pb_msgdesc_t PB_App_LockStatusResponse_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define PB_App_StartRequest_fields &PB_App_StartRequest_msg
#define PB_App_LockStatusRequest_fields &PB_App_LockStatusRequest_msg
#define PB_App_LockStatusResponse_fields &PB_App_LockStatusResponse_msg
/* Maximum encoded size of messages (where known) */
/* PB_App_StartRequest_size depends on runtime parameters */
#define PB_App_LockStatusRequest_size 0
#define PB_App_LockStatusResponse_size 2
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

Some files were not shown because too many files have changed in this diff Show More