From f463a4d510add54e3d080adaa1acfa2c36069440 Mon Sep 17 00:00:00 2001 From: Michael Ehrenreich Date: Tue, 21 Feb 2023 02:23:57 +0100 Subject: [PATCH] More loading and saving projects --- dmxcontroller.cpp | 75 ++++++++ dmxcontroller.h | 2 + projectloader.cpp | 467 ++++++++++++++++++++++++++++++++++++++++++++++ projectloader.h | 237 +---------------------- 4 files changed, 550 insertions(+), 231 deletions(-) diff --git a/dmxcontroller.cpp b/dmxcontroller.cpp index a3e3389..760c51a 100644 --- a/dmxcontroller.cpp +++ b/dmxcontroller.cpp @@ -4,8 +4,11 @@ #include #include +#include #include +#include "projectloader.h" + DmxController::DmxController(QObject *parent) : QObject{parent}, m_thread{*this}, @@ -127,8 +130,80 @@ DmxController::DmxController(QObject *parent) : { } +bool DmxController::loadProject(QString name) { + QFile readJsonFile(name); + if (!readJsonFile.exists()) + { + qDebug() << "Project file does not exist"; + return false; + } + + if (!readJsonFile.open(QIODevice::ReadOnly)) + { + qDebug() << "Error opening project file: " << readJsonFile.errorString(); + return false; + } + + QByteArray json = readJsonFile.readAll(); + if (json.size() == 0) + { + qDebug() << "Error reading project file"; + return false; + } + + QJsonParseError error; + QJsonDocument jd = QJsonDocument::fromJson(json, &error); + if (jd.isNull()) + { + qDebug() << "Error parsing JSON: " << error.errorString(); + return false; + } + + if (auto proj = ProjectLoader::loadProject(jd); proj) + { + m_lightProject = proj.value(); + } + else + { + qDebug() << proj.error(); + return false; + } + + return true; +} + +bool DmxController::saveProject(QString name) +{ + QFile jsonFile(name); + if (!jsonFile.open(QIODevice::ReadWrite)) + { + qDebug() << "Error opening file: " << jsonFile.errorString(); + return false; + } + + auto proj = ProjectLoader::saveProject(m_lightProject); + if (!proj) + { + qDebug() << proj.error(); + return false; + } + + QByteArray json = proj.value().toJson(); + if (jsonFile.write(json) != json.size()) + { + qDebug() << "Error writing file: " << jsonFile.errorString(); + return false; + } + + return true; +} + bool DmxController::start() { + saveProject("project_default.json"); + + loadProject("project.json"); + m_serialPort.setPortName("/dev/ttyAMA0"); if (!m_serialPort.setBaudRate(250000)) { diff --git a/dmxcontroller.h b/dmxcontroller.h index d810940..d007827 100644 --- a/dmxcontroller.h +++ b/dmxcontroller.h @@ -16,6 +16,8 @@ class DmxController : public QObject public: explicit DmxController(QObject *parent = nullptr); + bool loadProject(QString name); + bool saveProject(QString name); bool start(); Q_INVOKABLE void setRegisterGroupSlider(int registerGroupId, quint8 value); diff --git a/projectloader.cpp b/projectloader.cpp index fded6b4..f3cf80f 100644 --- a/projectloader.cpp +++ b/projectloader.cpp @@ -1 +1,468 @@ #include "projectloader.h" + +namespace ProjectLoader { + +namespace { + +namespace detail { + +template class Ref> +struct is_specialization_t : std::false_type {}; + +template class Ref, typename... Args> +struct is_specialization_t, Ref>: std::true_type {}; + +// broken +//template +//constexpr bool is_specialization_v = is_specialization_t::value; + +template +concept Listific = is_specialization_t::value || + is_specialization_t::value || + std::same_as || + std::same_as || + std::same_as; + +template +concept OtherLoadableFromArray = std::same_as; + +template +concept LoadableFromArray = Listific || OtherLoadableFromArray; + +template +concept LoadableFromObject = std::same_as || + std::same_as || + std::same_as || + std::same_as || + std::same_as; + +template +concept JsonNumber = (std::integral && !std::same_as && !std::is_enum_v) || + std::floating_point; + +// TODO: Can we match on Q_ENUMs only? +template +concept LoadableQEnum = std::is_enum_v; + +} // namespace detail + +// forward declarations for loading stuff from QJsonValue +template +std::expected load(const QJsonValue &json); +template +std::expected load(const QJsonValue &json); +template +std::expected load(const QJsonValue &json); + +template +std::expected load(const QJsonArray &json) { + T result; + + for (size_t i = 0; i < json.size(); i++) { + const auto &el = json[i]; + auto res = load(el); + if (res.has_value()) { + result.push_back(res.value()); + } else { + return std::unexpected(QString("%1: %2").arg(i).arg(res.error())); + } + } + + return result; +} + +template T> +std::expected load(const QJsonArray &json) { + QVector3D vec; + + if (json.size() != 3) { + return std::unexpected("Vector has wrong number of components"); + } + + for (size_t i = 0; i < json.size(); i++) { + const auto &el = json[i]; + if (auto val = load(el); val) { + vec[i] = val.value(); + } else { + return std::unexpected(QString("%1: %2").arg(i).arg(val.error())); + } + } + + return vec; +} + +template +std::expected load(const QJsonValue &json) { + if (!json.isArray()) + return std::unexpected("Not a JSON array"); + return load(json.toArray()); +} + +template +std::expected load(const QJsonValue &json) { + if (json.isDouble()) { + return (T)json.toDouble(); + } else { + return std::unexpected(QString("Not a number")); + } +} + +template T> +std::expected load(const QJsonValue &json) { + if (json.isString()) { + return json.toString(); + } else { + return std::unexpected(QString("Not a string")); + } +} + +template +std::expected load(const QJsonValue &json) { + if (!json.isString()) { + return std::unexpected("Not a JSON string"); + } + + QMetaEnum me = QMetaEnum::fromType(); + if (!me.isValid()) { + return std::unexpected("Invalid QMetaEnum"); + } + + bool ok = false; + auto value = me.keyToValue(json.toString().toStdString().c_str(), &ok); + if (ok) { + return (T)value; + } else { + return std::unexpected(QString("No value found for enum key %1").arg(json.toString())); + } +} + +template +std::expected loadIfExists(const QJsonObject &json, const QString &key) { + if (json.contains(key)) { + return load(json[key]); + } else { + return std::unexpected(QString("does not exist")); + } +} + +template T> +std::expected load(const QJsonObject &json) { + LightProject lp; + + if (auto deviceTypes = loadIfExists(json, "deviceTypes"); deviceTypes) { + lp.deviceTypes = std::move(deviceTypes.value()); + } else { + return std::unexpected(QString("deviceTypes: %1").arg(deviceTypes.error())); + } + + if (auto devices = loadIfExists(json, "devices"); devices) { + lp.devices = std::move(devices.value()); + } else { + return std::unexpected(QString("devices: %1").arg(devices.error())); + } + + if (auto registerGroups = loadIfExists(json, "registerGroups"); registerGroups) { + lp.registerGroups = std::move(registerGroups.value()); + } else { + return std::unexpected(QString("registerGroups: %1").arg(registerGroups.error())); + } + + return lp; +} + +template T> +std::expected load(const QJsonObject &json) { + DeviceTypeConfig dtc; + + if (auto val = loadIfExists(json, "id"); val) { + dtc.id = val.value(); + } else { + return std::unexpected(QString("id: %1").arg(val.error())); + } + + if (auto val = loadIfExists(json, "name"); val) { + dtc.name = val.value(); + } else { + return std::unexpected(QString("name: %1").arg(val.error())); + } + + if (auto val = loadIfExists(json, "iconName"); val) { + dtc.iconName = val.value(); + } else { + return std::unexpected(QString("iconName: %1").arg(val.error())); + } + + if (auto val = loadIfExists(json, "registers"); val) { + dtc.registers = std::move(val.value()); + } else { + return std::unexpected(QString("registers: %1").arg(val.error())); + } + + return dtc; +} + +template T> +std::expected load(const QJsonObject &json) { + DeviceTypeRegisterConfig dtrc; + if (auto val = loadIfExists(json, "type"); val) { + dtrc.type = val.value(); + } else { + return std::unexpected(QString("type: %1").arg(val.error())); + } + + return dtrc; +} + +template T> +std::expected load(const QJsonObject &json) { + DeviceConfig dc; + if (auto val = loadIfExists(json, "id"); val) { + dc.id = val.value(); + } else { + return std::unexpected(QString("id: %1").arg(val.error())); + } + if (auto val = loadIfExists(json, "name"); val) { + dc.name = val.value(); + } else { + return std::unexpected(QString("name: %1").arg(val.error())); + } + if (auto val = loadIfExists(json, "deviceTypeId"); val) { + dc.deviceTypeId = val.value(); + } else { + return std::unexpected(QString("deviceTypeId: %1").arg(val.error())); + } + if (auto val = loadIfExists(json, "address"); val) { + dc.address = val.value(); + } else { + return std::unexpected(QString("address: %1").arg(val.error())); + } + if (auto val = loadIfExists(json, "position"); val) { + dc.position = val.value(); + } else { + return std::unexpected(QString("position: %1").arg(val.error())); + } + + return dc; +} + +template T> +std::expected load(const QJsonObject &json) { + RegisterGroupConfig rgc; + if (auto val = loadIfExists(json, "id"); val) { + rgc.id = val.value(); + } else { + return std::unexpected(QString("id: %1").arg(val.error())); + } + if (auto val = loadIfExists(json, "name"); val) { + rgc.name = val.value(); + } else { + return std::unexpected(QString("name: %1").arg(val.error())); + } + if (auto val = loadIfExists(json, "sliders"); val) { + rgc.sliders = val.value(); + } else { + return std::unexpected(QString("sliders: %1").arg(val.error())); + } + + return rgc; +} + +template +std::expected load(const QJsonValue &json) { + if (!json.isObject()) + return std::unexpected("Not a JSON object"); + return load(json.toObject()); +} + +} // namespace + +std::expected loadProject(const QJsonDocument &json) { + if (!json.isObject()) { + return std::unexpected("JsonDocument is not an object"); + } + + return load(json.object()); +} + +namespace { +template +std::expected save(const T &val); + +template +std::expected save(const T &val) { + return QJsonValue((double)val); +} + +template T> +std::expected save(const T &val) { + return QJsonValue(val); +} + +template T> +std::expected save(const T &val) { + return QJsonArray{val.x(), val.y(), val.z()}; +} + +template +std::expected save(const T &val) { + QMetaEnum me = QMetaEnum::fromType(); + if (!me.isValid()) { + return std::unexpected("Invalid QMetaEnum"); + } + + if (auto key = me.valueToKey((int)val); key) { + return QJsonValue(key); + } else { + return std::unexpected(QString("No key found for enum value %1").arg((int)val)); + } +} + +template T> +std::expected save(const T &val) { + QJsonObject json; + + if (auto id = save(val.id); id) { + json["id"] = id.value(); + } else { + return std::unexpected(QString("id: %1").arg(id.error())); + } + + if (auto name = save(val.name); name) { + json["name"] = name.value(); + } else { + return std::unexpected(QString("name: %1").arg(name.error())); + } + + if (auto iconName = save(val.iconName); iconName) { + json["iconName"] = iconName.value(); + } else { + return std::unexpected(QString("iconName: %1").arg(iconName.error())); + } + + if (auto registers = save(val.registers); registers) { + json["registers"] = registers.value(); + } else { + return std::unexpected(QString("registers: %1").arg(registers.error())); + } + + return json; +} + +template T> +std::expected save(const T &val) { + QJsonObject json; + if (auto type = save(val.type); type) { + json["type"] = type.value(); + } else { + return std::unexpected(QString("type: %1").arg(type.error())); + } + + return json; +} + +template T> +std::expected save(const T &val) { + QJsonObject json; + if (auto id = save(val.id); id) { + json["id"] = id.value(); + } else { + return std::unexpected(QString("id: %1").arg(id.error())); + } + if (auto name = save(val.name); name) { + json["name"] = name.value(); + } else { + return std::unexpected(QString("name: %1").arg(name.error())); + } + if (auto deviceTypeId = save(val.deviceTypeId); deviceTypeId) { + json["deviceTypeId"] = deviceTypeId.value(); + } else { + return std::unexpected(QString("deviceTypeId: %1").arg(deviceTypeId.error())); + } + if (auto address = save(val.address); address) { + json["address"] = address.value(); + } else { + return std::unexpected(QString("address: %1").arg(address.error())); + } + if (auto position = save(val.position); position) { + json["position"] = position.value(); + } else { + return std::unexpected(QString("position: %1").arg(position.error())); + } + + return json; +} + +template T> +std::expected save(const T &val) { + QJsonObject json; + if (auto id = save(val.id); id) { + json["id"] = id.value(); + } else { + return std::unexpected(QString("id: %1").arg(id.error())); + } + if (auto name = save(val.name); name) { + json["name"] = name.value(); + } else { + return std::unexpected(QString("name: %1").arg(name.error())); + } + if (auto sliders = save(val.sliders); sliders) { + json["sliders"] = sliders.value(); + } else { + return std::unexpected(QString("sliders: %1").arg(sliders.error())); + } + + return json; +} + +template +std::expected save(const T &val) { + QJsonArray arr; + + for (size_t i = 0; i < val.size(); i++) { + const auto &el = val[i]; + auto json = save(el); + if (json) { + arr.push_back(json.value()); + } else { + return std::unexpected(QString("%1: %2").arg(i).arg(json.error())); + } + } + + return arr; +} + +template T> +std::expected save(const T &val) { + QJsonObject json; + if (auto deviceTypes = save(val.deviceTypes); deviceTypes) { + json["deviceTypes"] = deviceTypes.value(); + } else { + return std::unexpected(QString("deviceTypes: %1").arg(deviceTypes.error())); + } + + if (auto devices = save(val.devices); devices) { + json["devices"] = devices.value(); + } else { + return std::unexpected(QString("devices: %1").arg(devices.error())); + } + + if (auto registerGroups = save(val.registerGroups); registerGroups) { + json["registerGroups"] = registerGroups.value(); + } else { + return std::unexpected(QString("registerGroups: %1").arg(registerGroups.error())); + } + + return json; +} + +} // namespace + +std::expected saveProject(const LightProject &val) { + if (auto project = save(val); project) { + return QJsonDocument(project.value()); + } else { + return std::unexpected(QString("Failed to save project: %1").arg(project.error())); + } +} + +} // namespace ProjectLoader diff --git a/projectloader.h b/projectloader.h index ac7e721..5daa7f8 100644 --- a/projectloader.h +++ b/projectloader.h @@ -5,245 +5,20 @@ #include #include +#include #include #include "lightproject.h" +#include "projectloader.h" + namespace ProjectLoader { - namespace { - namespace detail { - template class Ref> - struct is_specialization_t : std::false_type {}; - template class Ref, typename... Args> - struct is_specialization_t, Ref>: std::true_type {}; +std::expected loadProject(const QJsonDocument &json); - // broken - //template - //constexpr bool is_specialization_v = is_specialization_t::value; +std::expected saveProject(const LightProject &val); - template - concept Listific = is_specialization_t::value || - is_specialization_t::value || - std::same_as || - std::same_as || - std::same_as; - - template - concept OtherLoadableFromArray = std::same_as; - - template - concept LoadableFromArray = Listific || OtherLoadableFromArray; - - template - concept LoadableFromObject = std::same_as || - std::same_as || - std::same_as || - std::same_as || - std::same_as; - - template - concept JsonNumber = (std::integral && !std::same_as && !std::is_enum_v) || - std::floating_point; - - } - - template - std::expected load(const QJsonValue &json) { - if (!json.isArray()) - return std::unexpected("Not a JSON array"); - return load(json.toArray()); - } - - template - std::expected load(const QJsonValue &json) { - if (!json.isObject()) - return std::unexpected("Not a JSON object"); - return load(json.toObject()); - } - - template - std::expected load(const QJsonArray &json) { - T result; - - for (size_t i = 0; i < json.size(); i++) { - auto &el = json[i]; - auto res = load(el); - if (res.has_value()) { - result.push_back(res.value()); - } else { - return std::unexpected(QString("%1: %2").arg(i).arg(res.error())); - } - } - - return result; - } - - template - std::expected load(const QJsonValue &json) { - if (json.isDouble()) { - return (T)json.toDouble(); - } else { - return std::unexpected(QString("Not a number")); - } - } - - template T> - std::expected load(const QJsonValue &json) { - if (json.isString()) { - return json.toString(); - } else { - return std::unexpected(QString("Not a string")); - } - } - - template T> - std::expected load(const QJsonArray &json) { - QVector3D vec; - - if (json.size() != 3) { - return std::unexpected("Vector has wrong number of components"); - } - - for (size_t i = 0; i < json.size(); i++) { - auto &el = json[i]; - if (auto val = load(el); val) { - vec[i] = val.value(); - } else { - return std::unexpected(QString("%1: %2").arg(i).arg(val.error())); - } - } - - return vec; - } - - template - std::expected loadIfExists(const QJsonObject &json, const QString &key) { - if (json.contains(key)) { - return load(json[key]); - } else { - return std::unexpected(QString("does not exist")); - } - } - - template T> - std::expected load(const QJsonObject &json) { - LightProject lp; - - if (auto deviceTypes = loadIfExists(json, "deviceTypes"); deviceTypes) { - lp.deviceTypes = std::move(deviceTypes.value()); - } else { - return std::unexpected(QString("deviceTypes: %1").arg(deviceTypes.error())); - } - - if (auto devices = loadIfExists(json, "devices"); devices) { - lp.devices = std::move(devices.value()); - } else { - return std::unexpected(QString("devices: %1").arg(devices.error())); - } - - return lp; - } - - template T> - std::expected load(const QJsonObject &json) { - DeviceTypeConfig dtc; - - if (auto val = loadIfExists(json, "id"); val) { - dtc.id = val.value(); - } else { - return std::unexpected(QString("id: %1").arg(val.error())); - } - - if (auto val = loadIfExists(json, "name"); val) { - dtc.name = val.value(); - } else { - return std::unexpected(QString("name: %1").arg(val.error())); - } - - if (auto val = loadIfExists(json, "iconName"); val) { - dtc.iconName = val.value(); - } else { - return std::unexpected(QString("iconName: %1").arg(val.error())); - } - - if (auto val = loadIfExists(json, "registers"); val) { - dtc.registers = std::move(val.value()); - } else { - return std::unexpected(QString("registers: %1").arg(val.error())); - } - - return dtc; - } - - template T> - std::expected load(const QJsonObject &json) { - DeviceTypeRegisterConfig dtrc; - if (auto val = loadIfExists(json, "type"); val) { - dtrc.type = val.value(); - } else { - return std::unexpected(QString("type: %1").arg(val.error())); - } - } - - template T> - std::expected load(const QJsonObject &json) { - DeviceConfig dc; - if (auto val = loadIfExists(json, "id"); val) { - dc.id = val.value(); - } else { - return std::unexpected(QString("id: %1").arg(val.error())); - } - if (auto val = loadIfExists(json, "name"); val) { - dc.name = val.value(); - } else { - return std::unexpected(QString("name: %1").arg(val.error())); - } - if (auto val = loadIfExists(json, "deviceTypeId"); val) { - dc.deviceTypeId = val.value(); - } else { - return std::unexpected(QString("deviceTypeId: %1").arg(val.error())); - } - if (auto val = loadIfExists(json, "address"); val) { - dc.address = val.value(); - } else { - return std::unexpected(QString("address: %1").arg(val.error())); - } - if (auto val = loadIfExists(json, "position"); val) { - dc.position = val.value(); - } else { - return std::unexpected(QString("position: %1").arg(val.error())); - } - - return dc; - } - - template T> - std::expected load(const QJsonObject &json) { - RegisterGroupConfig rgc; - if (auto val = loadIfExists(json, "id"); val) { - rgc.id = val.value(); - } else { - return std::unexpected(QString("id: %1").arg(val.error())); - } - if (auto val = loadIfExists(json, "name"); val) { - rgc.name = val.value(); - } else { - return std::unexpected(QString("name: %1").arg(val.error())); - } - if (auto val = loadIfExists(json, "sliders"); val) { - rgc.sliders = val.value(); - } else { - return std::unexpected(QString("sliders: %1").arg(val.error())); - } - - return rgc; - } - } // namespace - - std::expected loadProject(const QJsonObject &json) { - return load(json); - } } // namespace ProjectLoader + #endif // PROJECTLOADER_H