Architectural changes to allow using JSON maps to reduce MCU serial usage

This commit is contained in:
Alex Spataru 2020-12-24 17:27:56 -06:00
parent 9d7ee95446
commit ba03e7b91e
4 changed files with 199 additions and 4 deletions

View File

@ -66,10 +66,18 @@ Widgets.Window {
id: commAuto
checked: true
text: qsTr("Auto (JSON from serial device)")
onCheckedChanged: {
if (checked)
CppJsonParser.setOperationMode(CppJsonParser.kAutomatic)
}
} RadioButton {
id: commManual
checked: false
text: qsTr("Manual (use JSON map file)")
onCheckedChanged: {
if (checked)
CppJsonParser.setOperationMode(CppJsonParser.kManual)
}
}
//
@ -79,8 +87,10 @@ Widgets.Window {
Layout.fillWidth: true
opacity: enabled ? 1 : 0.5
enabled: commManual.checked
text: qsTr("Select map file") + "..."
onClicked: CppJsonParser.loadJsonMap()
Behavior on opacity {NumberAnimation{}}
text: CppJsonParser.jsonMapFilename.length ? qsTr("Change map file (%1)").arg(CppJsonParser.jsonMapFilename) :
qsTr("Select map file") + "..."
}
//

View File

@ -23,17 +23,40 @@
#include "JsonParser.h"
#include "SerialManager.h"
#include <QFileInfo>
#include <QFileDialog>
#include <QMessageBox>
/*
* Only instance of the class
*/
static JsonParser *INSTANCE = nullptr;
/**
* Shows a macOS-like message box with the given properties
*/
static int NiceMessageBox(QString text, QString informativeText, QString windowTitle = qAppName(),
QMessageBox::StandardButtons buttons = QMessageBox::Ok)
{
auto icon = QPixmap(":/images/icon.png").scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
QMessageBox box;
box.setIconPixmap(icon);
box.setWindowTitle(windowTitle);
box.setStandardButtons(buttons);
box.setText("<h3>" + text + "</h3>");
box.setInformativeText(informativeText);
return box.exec();
}
/**
* Initializes the JSON Parser class and connects
* appropiate SIGNALS/SLOTS
*/
JsonParser::JsonParser()
{
m_opMode = kAutomatic;
auto sm = SerialManager::getInstance();
connect(sm, SIGNAL(packetReceived(QByteArray)), this, SLOT(readData(QByteArray)));
}
@ -49,16 +72,147 @@ JsonParser *JsonParser::getInstance()
return INSTANCE;
}
/**
* Returns the JSON map data from the loaded file as a string
*/
QByteArray JsonParser::jsonMapData() const
{
return m_jsonMapData;
}
/**
* Returns the parsed JSON document from the received packet
*/
QJsonDocument JsonParser::document()
QJsonDocument JsonParser::document() const
{
return m_document;
}
/**
* Tries to parse the given data as a JSON document.
* Returns the file name (e.g. "JsonMap.json") of the loaded JSON
* map file
*/
QString JsonParser::jsonMapFilename() const
{
if (m_jsonMap.isOpen())
{
auto fileInfo = QFileInfo(m_jsonMap.fileName());
return fileInfo.fileName();
}
return "";
}
/**
* Returns the file path of the loaded JSON map file
*/
QString JsonParser::jsonMapFilepath() const
{
if (m_jsonMap.isOpen())
{
auto fileInfo = QFileInfo(m_jsonMap.fileName());
return fileInfo.filePath();
}
return "";
}
/**
* Returns the operation mode
*/
JsonParser::OperationMode JsonParser::operationMode() const
{
return m_opMode;
}
/**
* Creates a file dialog & lets the user select the JSON file map
*/
void JsonParser::loadJsonMap()
{
auto file = QFileDialog::getOpenFileName(Q_NULLPTR, tr("Select JSON map file"), QDir::homePath(),
tr("JSON files (*.json)"));
if (!file.isEmpty())
loadJsonMap(file);
}
/**
* Opens, validates & loads into memory the JSON file in the given @a path.
*/
void JsonParser::loadJsonMap(const QString &path)
{
// Validate path
if (path.isEmpty())
return;
// Close previous file (if open)
if (m_jsonMap.isOpen())
{
m_jsonMap.close();
emit jsonFileMapChanged();
}
// Try to open the file (read only mode)
m_jsonMap.setFileName(path);
if (m_jsonMap.open(QFile::ReadOnly))
{
// Read data & validate JSON from file
QJsonParseError error;
auto data = m_jsonMap.readAll();
auto document = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError)
{
m_jsonMap.close();
NiceMessageBox(tr("JSON parse error"), error.errorString());
}
// JSON contains no errors
else
{
m_jsonMapData = data;
NiceMessageBox(tr("JSON map file loaded successfully!"),
tr("File \"%1\" loaded into memory").arg(jsonMapFilename()));
}
}
// Open error
else
{
NiceMessageBox(tr("Cannot read file contents"),
tr("Failed to read contents of \"%1\", check file permissions").arg(jsonMapFilename()));
m_jsonMap.close();
}
// Update UI
emit jsonFileMapChanged();
}
/**
* Changes the operation mode of the JSON parser. There are two possible op. modes:
*
* @c kManual serial data only contains the comma-separated values, and we need
* to use a JSON map file (given by the user) to know what each value means.
* This method is recommended when we need to transfer & display a large amount
* of information from the microcontroller unit to the computer.
*
* @c kAuto serial data contains the JSON data frame, good for simple applications or
* for prototyping.
*/
void JsonParser::setOperationMode(const OperationMode mode)
{
m_opMode = mode;
emit operationModeChanged();
}
/**
* Tries to parse the given data as a JSON document according to the selected
* operation mode.
*
* Possible operation modes:
* - Auto: serial data contains the JSON data frame
* - Manual: serial data only contains the comma-separated values, and we need
* to use a JSON map file (given by the user) to know what each value means
*
* If JSON parsing is successfull, then the class shall notify the rest of the
* application in order to process packet data.
*/

View File

@ -23,7 +23,9 @@
#ifndef JSON_PARSER_H
#define JSON_PARSER_H
#include <QFile>
#include <QObject>
#include <QQmlEngine>
#include <QJsonArray>
#include <QJsonValue>
#include <QJsonObject>
@ -32,13 +34,36 @@
class JsonParser : public QObject
{
Q_OBJECT
Q_PROPERTY(QString jsonMapFilename READ jsonMapFilename NOTIFY jsonFileMapChanged)
Q_PROPERTY(QString jsonMapFilepath READ jsonMapFilepath NOTIFY jsonFileMapChanged)
Q_PROPERTY(OperationMode operationMode READ operationMode WRITE setOperationMode NOTIFY operationModeChanged)
signals:
void packetReceived();
void jsonFileMapChanged();
void operationModeChanged();
public:
enum OperationMode
{
kManual,
kAutomatic,
};
Q_ENUMS(OperationMode)
public:
static JsonParser *getInstance();
QJsonDocument document();
QByteArray jsonMapData() const;
QJsonDocument document() const;
QString jsonMapFilename() const;
QString jsonMapFilepath() const;
OperationMode operationMode() const;
public slots:
void loadJsonMap();
void loadJsonMap(const QString &path);
void setOperationMode(const OperationMode mode);
private:
JsonParser();
@ -47,7 +72,10 @@ private slots:
void readData(const QByteArray &data);
private:
QFile m_jsonMap;
OperationMode m_opMode;
QJsonDocument m_document;
QByteArray m_jsonMapData;
};
#endif

View File

@ -34,6 +34,7 @@
#include "AppInfo.h"
#include "Widgets.h"
#include "QmlBridge.h"
#include "JsonParser.h"
#include "GraphProvider.h"
#include "SerialManager.h"
@ -64,6 +65,7 @@ int main(int argc, char **argv)
auto widgets = Widgets::getInstance();
auto csvExport = Export::getInstance();
auto qmlBridge = QmlBridge::getInstance();
auto jsonParser = JsonParser::getInstance();
auto updater = QSimpleUpdater::getInstance();
auto graphProvider = GraphProvider::getInstance();
auto serialManager = SerialManager::getInstance();
@ -78,6 +80,7 @@ int main(int argc, char **argv)
engine.rootContext()->setContextProperty("CppWidgets", widgets);
engine.rootContext()->setContextProperty("CppExport", csvExport);
engine.rootContext()->setContextProperty("CppQmlBridge", qmlBridge);
engine.rootContext()->setContextProperty("CppJsonParser", jsonParser);
engine.rootContext()->setContextProperty("CppGraphProvider", graphProvider);
engine.rootContext()->setContextProperty("CppSerialManager", serialManager);
engine.rootContext()->setContextProperty("CppAppName", app.applicationName());