From 64cff3ce7850707d8af55ad8639b6e3da21fa7f2 Mon Sep 17 00:00:00 2001 From: 0xFEEDC0DE64 Date: Wed, 15 Feb 2023 01:12:19 +0100 Subject: [PATCH] Implemented basic functionality --- CMakeLists.txt | 49 +++++++ DeviceTypesSettingsPage.qml | 60 ++++++++ DevicesSettingsPage.qml | 67 +++++++++ EditableListView.qml | 72 ++++++++++ HomePage.qml | 71 +++++++++ LightControlWindow.qml | 189 ++++++++++++++++++++++++ README.md | 10 ++ SettingsPage.qml | 47 ++++++ devicesmodel.cpp | 280 ++++++++++++++++++++++++++++++++++++ devicesmodel.h | 35 +++++ devicetypesmodel.cpp | 241 +++++++++++++++++++++++++++++++ devicetypesmodel.h | 35 +++++ dmxcontroller.cpp | 109 ++++++++++++++ dmxcontroller.h | 44 ++++++ dmxcontrollerthread.cpp | 15 ++ dmxcontrollerthread.h | 18 +++ lightproject.cpp | 1 + lightproject.h | 27 ++++ main-dev.qml | 5 + main.cpp | 61 ++++++++ main.qml | 5 + 21 files changed, 1441 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 DeviceTypesSettingsPage.qml create mode 100644 DevicesSettingsPage.qml create mode 100644 EditableListView.qml create mode 100644 HomePage.qml create mode 100644 LightControlWindow.qml create mode 100644 SettingsPage.qml create mode 100644 devicesmodel.cpp create mode 100644 devicesmodel.h create mode 100644 devicetypesmodel.cpp create mode 100644 devicetypesmodel.h create mode 100644 dmxcontroller.cpp create mode 100644 dmxcontroller.h create mode 100644 dmxcontrollerthread.cpp create mode 100644 dmxcontrollerthread.h create mode 100644 lightproject.cpp create mode 100644 lightproject.h create mode 100644 main-dev.qml create mode 100644 main.cpp create mode 100644 main.qml diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..958c772 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required(VERSION 3.16) + +project(lightcontrol VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 6.2 REQUIRED COMPONENTS SerialPort Quick) + +qt_add_executable(applightcontrol + main.cpp + devicetypesmodel.h devicetypesmodel.cpp + dmxcontroller.h dmxcontroller.cpp + dmxcontrollerthread.h dmxcontrollerthread.cpp + lightproject.h lightproject.cpp + devicesmodel.h devicesmodel.cpp +) + +qt_add_qml_module(applightcontrol + URI lightcontrol + VERSION 1.0 + QML_FILES + main.qml + main-dev.qml + HomePage.qml + SettingsPage.qml + LightControlWindow.qml + EditableListView.qml + DeviceTypesSettingsPage.qml + DevicesSettingsPage.qml +) + +set_target_properties(applightcontrol PROPERTIES + MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE TRUE + WIN32_EXECUTABLE TRUE +) + +target_link_libraries(applightcontrol + PRIVATE + Qt6::SerialPort + Qt6::Quick +) + +install(TARGETS applightcontrol + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/DeviceTypesSettingsPage.qml b/DeviceTypesSettingsPage.qml new file mode 100644 index 0000000..45bec39 --- /dev/null +++ b/DeviceTypesSettingsPage.qml @@ -0,0 +1,60 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +ColumnLayout { + Label { + text: qsTr("Device Types Settings") + } + + RowLayout { + //Layout.fillWidth: true + Layout.fillHeight: true + + EditableListView { + id: listView + + Layout.preferredWidth: 300 + Layout.maximumWidth: 300 + Layout.fillHeight: true + + model: deviceTypesModel + + onAddClicked: (index) => console.log('added', index); + onRemoveClicked: (index) => deviceTypesModel.removeRow(index) + } + + ColumnLayout { + GridLayout { + Layout.preferredWidth: 300 + Layout.maximumWidth: 300 + + columns: 2 + + Label { text: qsTr("Id:") } + SpinBox { + Layout.fillWidth: true + value: listView.currentItem.myData.id + onValueModified: listView.currentItem.myData.id = value + } + Label { text: qsTr("Name:") } + TextField { + Layout.fillWidth: true + text: listView.currentItem.myData.name + onTextEdited: listView.currentItem.myData.name = text + } + Label { text: qsTr("Registers:") } + Pane { + Layout.fillWidth: true + Layout.fillHeight: true + EditableListView { + anchors.fill: parent + } + } + } + Item { + Layout.fillHeight: true + } + } + } +} diff --git a/DevicesSettingsPage.qml b/DevicesSettingsPage.qml new file mode 100644 index 0000000..86acb88 --- /dev/null +++ b/DevicesSettingsPage.qml @@ -0,0 +1,67 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +ColumnLayout { + Label { + text: qsTr("Devices Settings") + } + + RowLayout { + //Layout.fillWidth: true + Layout.fillHeight: true + + EditableListView { + id: listView + + Layout.preferredWidth: 300 + Layout.maximumWidth: 300 + Layout.fillHeight: true + + model: devicesModel + + onAddClicked: (index) => console.log('added', index) + onRemoveClicked: (index) => devicesModel.removeRow(index) + } + + ColumnLayout { + GridLayout { + Layout.preferredWidth: 300 + Layout.maximumWidth: 300 + + columns: 2 + + Label { text: qsTr("Id:") } + SpinBox { + Layout.fillWidth: true + value: listView.currentItem.myData.id + onValueModified: listView.currentItem.myData.id = value + } + Label { text: qsTr("Name:") } + TextField { + Layout.fillWidth: true + text: listView.currentItem.myData.name + onTextEdited: listView.currentItem.myData.name = text + } + Label { text: qsTr("DeviceType:") } + ComboBox { + Layout.fillWidth: true + model: deviceTypesModel + textRole: "name" + valueRole: "id" + // TODO + } + Label { text: qsTr("Address:") } + SpinBox { + Layout.fillWidth: true + value: listView.currentItem.myData.address + onValueModified: listView.currentItem.myData.address = value + } + Label { text: qsTr("Position:") } + } + Item { + Layout.fillHeight: true + } + } + } +} diff --git a/EditableListView.qml b/EditableListView.qml new file mode 100644 index 0000000..6264770 --- /dev/null +++ b/EditableListView.qml @@ -0,0 +1,72 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +ColumnLayout { + property alias model: listView.model + property alias currentIndex: listView.currentIndex + property alias currentItem: listView.currentItem + + signal addClicked(index: int) + signal removeClicked(index: int) + + RowLayout { + Layout.fillWidth: true + + Button { + text: qsTr("Add") + + onClicked: addClicked(listView.currentIndex) + } + + Item { + Layout.fillWidth: true + } + + Button { + text: qsTr("Remove") + + onClicked: removeClicked(listView.currentIndex) + enabled: listView.currentIndex >= 0 + } + } + + ListView { + id: listView + + Layout.fillWidth: true + Layout.fillHeight: true + + clip: true + spacing: 2 + + delegate: Rectangle { + property variant myData: model + + width: listView.width + height: 40 + + color: ListView.isCurrentItem ? + Material.color(Material.Purple) : + Material.color(Material.Purple, Material.Shade900) + radius: 5 + + Label { + anchors.fill: parent + //anchors.verticalCenter: parent.verticalCenter + id: text + text: model.name + padding: 5 + fontSizeMode: Text.VerticalFit + minimumPixelSize: 10; + font.pixelSize: 72 + } + + MouseArea { + anchors.fill: parent + onClicked: listView.currentIndex = index + } + } + focus: true + } +} diff --git a/HomePage.qml b/HomePage.qml new file mode 100644 index 0000000..4de12c6 --- /dev/null +++ b/HomePage.qml @@ -0,0 +1,71 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +RowLayout { + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + Flow { + anchors.centerIn: parent + + Repeater { + model: devicesModel + + ColumnLayout { + width: 50 + height: 200 + + Label { + Layout.fillWidth: true + text: index + horizontalAlignment: Text.AlignHCenter + } + + Slider { + id: slider + Layout.fillWidth: true + Layout.fillHeight: true + + orientation: Qt.Vertical + from: 0 + to: 255 + + onValueChanged: __controller.setChannel(32 + index, value) + } + + Label { + Layout.fillWidth: true + text: Math.round(slider.value) + horizontalAlignment: Text.AlignHCenter + } + } + } + } + } + + ColumnLayout { + Layout.preferredWidth: 100 + Layout.fillHeight: true + + Button { + Layout.preferredHeight: 100 + + text: qsTr('Settings') + + onClicked: stackview.push(settingsPage) + + Component { + id: settingsPage + + SettingsPage { + } + } + } + + Item { + Layout.fillHeight: true + } + } +} diff --git a/LightControlWindow.qml b/LightControlWindow.qml new file mode 100644 index 0000000..f2ed752 --- /dev/null +++ b/LightControlWindow.qml @@ -0,0 +1,189 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.VirtualKeyboard + +import com.büro 1.0 + +ApplicationWindow { + id: window + width: 1360 + height: 768 + title: qsTr("Hello World") + + Material.theme: Material.Dark + Material.accent: Material.Purple + + DeviceTypesModel { + id: deviceTypesModel + controller: __controller + } + + DevicesModel { + id: devicesModel + controller: __controller + } + + ColumnLayout { + anchors.fill: parent + + Pane { + Layout.fillWidth: true + Layout.preferredHeight: 75 + + Material.elevation: 6 + + RowLayout { + anchors.fill: parent + + Label { + Layout.fillWidth: true + Layout.fillHeight: true + + text: qsTr("Schein-Commander") + fontSizeMode: Text.VerticalFit + minimumPixelSize: 10; + font.pixelSize: 72 + } + + Label { + id: timeText + Layout.fillWidth: true + Layout.fillHeight: true + + text: Qt.formatTime(new Date(), "hh:mm:ss") + fontSizeMode: Text.VerticalFit + minimumPixelSize: 10; + font.pixelSize: 72 + + Timer { + id: timer + interval: 1000 + repeat: true + running: true + + onTriggered: timeText.text = Qt.formatTime(new Date(), "hh:mm:ss"); + } + } + + Button { + Layout.fillHeight: true + + text: qsTr("Back") + + onClicked: stackview.pop(); + enabled: stackview.depth > 1 + } + } + } + + StackView { + id: stackview + Layout.fillWidth: true + Layout.fillHeight: true + + initialItem: homePage + + Component { + id: homePage + + HomePage { + } + } + + pushEnter: Transition { + PropertyAnimation { + property: "opacity" + from: 0 + to:1 + duration: 200 + } + } + pushExit: Transition { + PropertyAnimation { + property: "opacity" + from: 1 + to:0 + duration: 200 + } + } + popEnter: Transition { + PropertyAnimation { + property: "opacity" + from: 0 + to:1 + duration: 200 + } + } + popExit: Transition { + PropertyAnimation { + property: "opacity" + from: 1 + to:0 + duration: 200 + } + } + } + } + + Button { + id: closeButton + + visible: inputPanel.active + + anchors.bottom: inputPanel.top + anchors.right: parent.right + z: 99 + + //onClicked: inputPanel.active = false +// onClicked: InputContext.priv.hideInputPanel() + + text: qsTr("Close keyboard?") + + focus: false + } + + InputPanel { + id: inputPanel + z: 99 + x: 0 + y: window.height + width: window.width + + states: State { + name: "visible" + when: inputPanel.active + PropertyChanges { + target: inputPanel + y: window.height - inputPanel.height + } + } + transitions: [ + Transition { + from: "visible" + to: "" + reversible: false + ParallelAnimation { + NumberAnimation { + properties: "y" + duration: 1000 + easing.type: Easing.OutBounce + } + } + }, + Transition { + from: "" + to: "visible" + reversible: false + ParallelAnimation { + NumberAnimation { + properties: "y" + duration: 1000 + easing.type: Easing.OutBounce + } + } + } + ] + } +} diff --git a/README.md b/README.md index c4290e3..422635e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,12 @@ # scheincommander A qt quick application to control dmx moving heads / lights / fog machines (running on a raspberry pi) + +## How to build + +``` +mkdir build +cd build +qmake6 .. +make -j4096 +./applightcontrol +``` diff --git a/SettingsPage.qml b/SettingsPage.qml new file mode 100644 index 0000000..f07f7a3 --- /dev/null +++ b/SettingsPage.qml @@ -0,0 +1,47 @@ +import QtQuick +import QtQuick.Controls.Material +import QtQuick.Layouts + +Item { + Label { + text: qsTr("Settings") + } + + RowLayout { + anchors.centerIn: parent + + Button { + id: button0 + text: qsTr("Device\nTypes") + + Layout.preferredWidth: 100 + Layout.preferredHeight: 100 + + onClicked: stackview.push(deviceTypesSettingsPage) + + Component { + id: deviceTypesSettingsPage + + DeviceTypesSettingsPage { + } + } + } + + Button { + id: button1 + text: qsTr("Devices") + + Layout.preferredWidth: 100 + Layout.preferredHeight: 100 + + onClicked: stackview.push(devicesSettingsPage) + + Component { + id: devicesSettingsPage + + DevicesSettingsPage { + } + } + } + } +} diff --git a/devicesmodel.cpp b/devicesmodel.cpp new file mode 100644 index 0000000..072a6be --- /dev/null +++ b/devicesmodel.cpp @@ -0,0 +1,280 @@ +#include "devicesmodel.h" + +#include +#include + +constexpr auto IdRole = Qt::UserRole; +constexpr auto LightTypeIdRole = Qt::UserRole + 1; +constexpr auto AddressRole = Qt::UserRole + 2; +constexpr auto PositionRole = Qt::UserRole + 3; + +DevicesModel::DevicesModel(QObject *parent) : + QAbstractItemModel{parent} +{ +} + +void DevicesModel::setController(DmxController *controller) +{ + if (m_controller != controller) + { + beginResetModel(); + m_controller = controller; + endResetModel(); + emit controllerChanged(m_controller); + } +} + +QModelIndex DevicesModel::index(int row, int column, const QModelIndex &parent) const +{ + if (parent.isValid()) + { + qWarning() << "hilfe" << __LINE__; + return {}; + } + + return createIndex(row, column, nullptr); +} + +QModelIndex DevicesModel::parent(const QModelIndex &child) const +{ + return {}; +} + +int DevicesModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + { + qWarning() << "hilfe" << __LINE__; + return -1; + } + + if (!m_controller) + return 0; + + return m_controller->lightProject().lights.size(); +} + +int DevicesModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + { + qWarning() << "hilfe" << __LINE__; + return -1; + } + return 1; +} + +QVariant DevicesModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + { + qWarning() << "hilfe" << __LINE__; + return {}; + } + + if (!m_controller) + { + qWarning() << "hilfe" << __LINE__; + return {}; + } + + if (index.row() < 0 || index.row() >= m_controller->lightProject().lights.size()) + { + qWarning() << "hilfe" << __LINE__; + return {}; + } + + if (index.column() != 0) + { + qWarning() << "hilfe" << __LINE__; + return {}; + } + + const auto &light = m_controller->lightProject().lights.at(index.row()); + + switch (role) + { + case Qt::DisplayRole: + case Qt::EditRole: return light.name; + case IdRole: return light.id; + case LightTypeIdRole: return light.lightTypeId; + case AddressRole: return light.address; + case PositionRole: return light.position; + } + + return {}; +} + +QMap DevicesModel::itemData(const QModelIndex &index) const +{ + if (!index.isValid()) + { + qWarning() << "hilfe" << __LINE__; + return {}; + } + + if (!m_controller) + { + qWarning() << "hilfe" << __LINE__; + return {}; + } + + if (index.row() < 0 || index.row() >= m_controller->lightProject().lights.size()) + { + qWarning() << "hilfe" << __LINE__; + return {}; + } + + if (index.column() != 0) + { + qWarning() << "hilfe" << __LINE__; + return {}; + } + + const auto &light = m_controller->lightProject().lights.at(index.row()); + + return { + { Qt::DisplayRole, light.name }, + { IdRole, light.id }, + { LightTypeIdRole, light.lightTypeId }, + { AddressRole, light.address }, + { PositionRole, light.position } + }; +} + +QHash DevicesModel::roleNames() const +{ + return { + { Qt::DisplayRole, "name" }, + { IdRole, "id" }, + { LightTypeIdRole, "lightTypeId" }, + { AddressRole, "address" }, + { PositionRole, "position" } + }; +} + +bool DevicesModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid()) + { + qWarning() << "hilfe" << __LINE__; + return false; + } + + if (!m_controller) + { + qWarning() << "hilfe" << __LINE__; + return false; + } + + auto &lights = m_controller->lightProject().lights; + if (index.row() < 0 || index.row() >= lights.size()) + { + qWarning() << "hilfe" << __LINE__; + return false; + } + + if (index.column() != 0) + { + qWarning() << "hilfe" << __LINE__; + return false; + } + + auto &light = lights.at(index.row()); + switch (role) + { + case Qt::DisplayRole: + case Qt::EditRole: + if (value.userType() != QMetaType::QString) + { + qWarning() << "hilfe" << __LINE__ << value.userType(); + return false; + } + light.name = value.toString(); + emit dataChanged(index, index, { Qt::DisplayRole, Qt::EditRole }); + return true; + case IdRole: + if (value.userType() != QMetaType::Int) + { + qWarning() << "hilfe" << __LINE__ << value.userType(); + return false; + } + light.id = value.toInt(); + emit dataChanged(index, index, { IdRole }); + return true; + case LightTypeIdRole: + if (value.userType() != QMetaType::Int) + { + qWarning() << "hilfe" << __LINE__ << value.userType(); + return false; + } + light.lightTypeId = value.toInt(); + emit dataChanged(index, index, { LightTypeIdRole }); + return true; + case AddressRole: + if (value.userType() != QMetaType::Int) + { + qWarning() << "hilfe" << __LINE__ << value.userType(); + return false; + } + light.address = value.toInt(); + emit dataChanged(index, index, { AddressRole }); + return true; + case PositionRole: + if (value.userType() != QMetaType::QVector3D) + { + qWarning() << "hilfe" << __LINE__ << value.userType(); + return false; + } + light.position = value.value(); + emit dataChanged(index, index, { AddressRole }); + return true; + } + + return false; +} + +bool DevicesModel::removeRows(int row, int count, const QModelIndex &parent) +{ + if (parent.isValid()) + { + qWarning() << "hilfe" << __LINE__; + return false; + } + + if (!m_controller) + { + qWarning() << "hilfe" << __LINE__; + return false; + } + + auto &lights = m_controller->lightProject().lights; + + if (row >= lights.size()) + { + qWarning() << "hilfe" << __LINE__; + return false; + } + + if (row + count >= lights.size()) + { + qWarning() << "hilfe" << __LINE__; + return false; + } + + beginRemoveRows({}, row, row+count-1); + auto begin = std::begin(lights) + row; + auto end = begin + count; + lights.erase(begin, end); + endRemoveRows(); + + return true; +} + +namespace { +void registerDenShit() +{ + qmlRegisterType("com.büro", 1, 0, "DevicesModel"); +} +} +Q_COREAPP_STARTUP_FUNCTION(registerDenShit) diff --git a/devicesmodel.h b/devicesmodel.h new file mode 100644 index 0000000..ffca2ff --- /dev/null +++ b/devicesmodel.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include "dmxcontroller.h" + +class DevicesModel : public QAbstractItemModel +{ + Q_OBJECT + Q_PROPERTY(DmxController* controller READ controller WRITE setController NOTIFY controllerChanged) + +public: + explicit DevicesModel(QObject *parent = nullptr); + + DmxController *controller() { return m_controller; } + const DmxController *controller() const { return m_controller; } + void setController(DmxController *controller); + + QModelIndex index(int row, int column, const QModelIndex &parent) const override; + QModelIndex parent(const QModelIndex &child) const override; + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + QMap itemData(const QModelIndex &index) const override; + QHash roleNames() const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + bool removeRows(int row, int count, const QModelIndex &parent) override; + +signals: + void controllerChanged(DmxController *controller); + +private: + DmxController *m_controller{}; +}; diff --git a/devicetypesmodel.cpp b/devicetypesmodel.cpp new file mode 100644 index 0000000..0f77687 --- /dev/null +++ b/devicetypesmodel.cpp @@ -0,0 +1,241 @@ +#include "devicetypesmodel.h" + +#include +#include + +constexpr auto IdRole = Qt::UserRole; + +DeviceTypesModel::DeviceTypesModel(QObject *parent) : + QAbstractItemModel{parent} +{ +} + +void DeviceTypesModel::setController(DmxController *controller) +{ + if (m_controller != controller) + { + beginResetModel(); + m_controller = controller; + endResetModel(); + emit controllerChanged(m_controller); + } +} + +QModelIndex DeviceTypesModel::index(int row, int column, const QModelIndex &parent) const +{ + if (parent.isValid()) + { + qWarning() << "hilfe" << __LINE__; + return {}; + } + + return createIndex(row, column, nullptr); +} + +QModelIndex DeviceTypesModel::parent(const QModelIndex &child) const +{ + return {}; +} + +int DeviceTypesModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + { + qWarning() << "hilfe" << __LINE__; + return -1; + } + + if (!m_controller) + return 0; + + return m_controller->lightProject().lightTypes.size(); +} + +int DeviceTypesModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + { + qWarning() << "hilfe" << __LINE__; + return -1; + } + return 1; +} + +QVariant DeviceTypesModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + { + qWarning() << "hilfe" << __LINE__; + return {}; + } + + if (!m_controller) + { + qWarning() << "hilfe" << __LINE__; + return {}; + } + + if (index.row() < 0 || index.row() >= m_controller->lightProject().lightTypes.size()) + { + qWarning() << "hilfe" << __LINE__; + return {}; + } + + if (index.column() != 0) + { + qWarning() << "hilfe" << __LINE__; + return {}; + } + + const auto &lightType = m_controller->lightProject().lightTypes.at(index.row()); + + switch (role) + { + case Qt::DisplayRole: + case Qt::EditRole: return lightType.name; + case IdRole: return lightType.id; + } + + return {}; +} + +QMap DeviceTypesModel::itemData(const QModelIndex &index) const +{ + if (!index.isValid()) + { + qWarning() << "hilfe" << __LINE__; + return {}; + } + + if (!m_controller) + { + qWarning() << "hilfe" << __LINE__; + return {}; + } + + if (index.row() < 0 || index.row() >= m_controller->lightProject().lightTypes.size()) + { + qWarning() << "hilfe" << __LINE__; + return {}; + } + + if (index.column() != 0) + { + qWarning() << "hilfe" << __LINE__; + return {}; + } + + const auto &lightType = m_controller->lightProject().lightTypes.at(index.row()); + + return { + { Qt::DisplayRole, lightType.name }, + { IdRole, lightType.id } + }; +} + +QHash DeviceTypesModel::roleNames() const +{ + return { + { Qt::DisplayRole, "name" }, + { IdRole, "id" } + }; +} + +bool DeviceTypesModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid()) + { + qWarning() << "hilfe" << __LINE__; + return false; + } + + if (!m_controller) + { + qWarning() << "hilfe" << __LINE__; + return false; + } + + auto &lightTypes = m_controller->lightProject().lightTypes; + if (index.row() < 0 || index.row() >= lightTypes.size()) + { + qWarning() << "hilfe" << __LINE__; + return false; + } + + if (index.column() != 0) + { + qWarning() << "hilfe" << __LINE__; + return false; + } + + auto &lightType = lightTypes.at(index.row()); + switch (role) + { + case Qt::DisplayRole: + case Qt::EditRole: + if (value.userType() != QMetaType::QString) + { + qWarning() << "hilfe" << __LINE__ << value.userType(); + return false; + } + lightType.name = value.toString(); + emit dataChanged(index, index, { Qt::DisplayRole, Qt::EditRole }); + return true; + case IdRole: + if (value.userType() != QMetaType::Int) + { + qWarning() << "hilfe" << __LINE__ << value.userType(); + return false; + } + lightType.id = value.toInt(); + emit dataChanged(index, index, { IdRole }); + return true; + } + + return false; +} + +bool DeviceTypesModel::removeRows(int row, int count, const QModelIndex &parent) +{ + if (parent.isValid()) + { + qWarning() << "hilfe" << __LINE__; + return false; + } + + if (!m_controller) + { + qWarning() << "hilfe" << __LINE__; + return false; + } + + auto &lightTypes = m_controller->lightProject().lightTypes; + + if (row >= lightTypes.size()) + { + qWarning() << "hilfe" << __LINE__; + return false; + } + + if (row + count > lightTypes.size()) + { + qWarning() << "hilfe" << __LINE__; + return false; + } + + beginRemoveRows({}, row, row+count-1); + auto begin = std::begin(lightTypes) + row; + auto end = begin + count; + lightTypes.erase(begin, end); + endRemoveRows(); + + return true; +} + +namespace { +void registerDenShit() +{ + qmlRegisterType("com.büro", 1, 0, "DeviceTypesModel"); +} +} +Q_COREAPP_STARTUP_FUNCTION(registerDenShit) diff --git a/devicetypesmodel.h b/devicetypesmodel.h new file mode 100644 index 0000000..a933464 --- /dev/null +++ b/devicetypesmodel.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include "dmxcontroller.h" + +class DeviceTypesModel : public QAbstractItemModel +{ + Q_OBJECT + Q_PROPERTY(DmxController* controller READ controller WRITE setController NOTIFY controllerChanged) + +public: + explicit DeviceTypesModel(QObject *parent = nullptr); + + DmxController *controller() { return m_controller; } + const DmxController *controller() const { return m_controller; } + void setController(DmxController *controller); + + QModelIndex index(int row, int column, const QModelIndex &parent) const override; + QModelIndex parent(const QModelIndex &child) const override; + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + QMap itemData(const QModelIndex &index) const override; + QHash roleNames() const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + bool removeRows(int row, int count, const QModelIndex &parent) override; + +signals: + void controllerChanged(DmxController *controller); + +private: + DmxController *m_controller{}; +}; diff --git a/dmxcontroller.cpp b/dmxcontroller.cpp new file mode 100644 index 0000000..1d7b711 --- /dev/null +++ b/dmxcontroller.cpp @@ -0,0 +1,109 @@ +#include "dmxcontroller.h" + +#include +#include + +#include + +DmxController::DmxController(QObject *parent) : + QObject{parent}, + m_thread{*this}, + m_lastInfo{QDateTime::currentDateTime()}, + m_counter{}, + m_lightProject { + .lightTypes { + { .id=0, .name="Stairville MH-X50+" }, + { .id=1, .name="RGBW Strahler" }, + { .id=2, .name="RGB Strahler" }, + { .id=3, .name="Nebelmaschine" } + }, + .lights { + { .id=0, .name="Lampe 1", .lightTypeId=1, .address=32 }, + { .id=1, .name="Lampe 2", .lightTypeId=1, .address=0 }, + { .id=2, .name="Lampe 3", .lightTypeId=1, .address=7 }, + { .id=3, .name="Lampe 4", .lightTypeId=1, .address=14 }, + { .id=4, .name="Moving Head 1", .lightTypeId=0, .address=40 }, + { .id=5, .name="Moving Head 2", .lightTypeId=0, .address=43 }, + { .id=6, .name="Moving Head 3", .lightTypeId=0, .address=46 }, + { .id=7, .name="Moving Head 4", .lightTypeId=0, .address=49 }, + { .id=8, .name="Test 1", .lightTypeId=2, .address=70 }, + { .id=9, .name="Test 2", .lightTypeId=2, .address=72 }, + { .id=10, .name="Nebelmaschine", .lightTypeId=3, .address=80 } + } + } +{ + std::fill(std::begin(buf), std::end(buf), 0); + + buf[32] = 255; + buf[33] = 255; + buf[34] = 0; + buf[35] = 0; +// buf[36] = 255; +} + +bool DmxController::start() +{ + m_serialPort.setPortName("/dev/ttyAMA0"); + if (!m_serialPort.setBaudRate(250000)) + { + qCritical("setBaudRate() failed %s", qPrintable(m_serialPort.errorString())); + return false; + } + if (!m_serialPort.setDataBits(QSerialPort::Data8)) + { + qCritical("setDataBits() failed %s", qPrintable(m_serialPort.errorString())); + return false; + } + if (!m_serialPort.setParity(QSerialPort::NoParity)) + { + qCritical("setParity() failed %s", qPrintable(m_serialPort.errorString())); + return false; + } + if (!m_serialPort.setStopBits(QSerialPort::TwoStop)) + { + qCritical("setStopBits() failed %s", qPrintable(m_serialPort.errorString())); + return false; + } + if (!m_serialPort.setFlowControl(QSerialPort::NoFlowControl)) + { + qCritical("setFlowControl() failed %s", qPrintable(m_serialPort.errorString())); + return false; + } + if (!m_serialPort.open(QIODevice::WriteOnly)) + { + qCritical("open() failed %s", qPrintable(m_serialPort.errorString())); + return false; + } + + m_thread.start(QThread::TimeCriticalPriority); + + return true; +} + +void DmxController::setChannel(int channel, int value) +{ + buf[channel] = value; +} + +void DmxController::sendDmxBuffer() +{ + const auto now = QDateTime::currentDateTime(); + + m_serialPort.setBreakEnabled(true); + QThread::usleep(88); + m_serialPort.setBreakEnabled(false); + +// const auto written = write(m_serialPort.handle(), buf, std::size(buf)); + const auto written = m_serialPort.write(buf, std::size(buf)); + m_serialPort.flush(); +// qDebug("%lli written", written); + + m_counter++; + + if (m_lastInfo.msecsTo(now) >= 1000) + { + qInfo("%i per second ä", m_counter); + m_counter = 0; + m_lastInfo = now; + } +} diff --git a/dmxcontroller.h b/dmxcontroller.h new file mode 100644 index 0000000..69b93e3 --- /dev/null +++ b/dmxcontroller.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include +#include + +#include "dmxcontrollerthread.h" +#include "lightproject.h" + +class DmxController : public QObject +{ + Q_OBJECT + +public: + explicit DmxController(QObject *parent = nullptr); + + bool start(); + + Q_INVOKABLE void setChannel(int channel, int value); + + LightProject &lightProject() { return m_lightProject; } + const LightProject &lightProject() const { return m_lightProject; } + + QReadWriteLock &projectLock() { return m_projectLock; } + +protected: + friend class DmxControllerThread; + + void sendDmxBuffer(); // runs in its own thread + +private: + QSerialPort m_serialPort; + + DmxControllerThread m_thread; + + char buf[513]; + + LightProject m_lightProject; + QReadWriteLock m_projectLock; + + QDateTime m_lastInfo; + int m_counter; +}; diff --git a/dmxcontrollerthread.cpp b/dmxcontrollerthread.cpp new file mode 100644 index 0000000..691be88 --- /dev/null +++ b/dmxcontrollerthread.cpp @@ -0,0 +1,15 @@ +#include "dmxcontrollerthread.h" + +#include "dmxcontroller.h" + +DmxControllerThread::DmxControllerThread(DmxController &controller) : + QThread{&controller}, + m_controller{controller} +{ +} + +void DmxControllerThread::run() +{ + while (!isInterruptionRequested()) + m_controller.sendDmxBuffer(); +} diff --git a/dmxcontrollerthread.h b/dmxcontrollerthread.h new file mode 100644 index 0000000..b65fa6e --- /dev/null +++ b/dmxcontrollerthread.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +class DmxController; + +class DmxControllerThread : public QThread +{ + Q_OBJECT + +public: + explicit DmxControllerThread(DmxController &controller); + + void run() override; + +private: + DmxController &m_controller; +}; diff --git a/lightproject.cpp b/lightproject.cpp new file mode 100644 index 0000000..ace1530 --- /dev/null +++ b/lightproject.cpp @@ -0,0 +1 @@ +#include "lightproject.h" diff --git a/lightproject.h b/lightproject.h new file mode 100644 index 0000000..0add29b --- /dev/null +++ b/lightproject.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include +#include + +struct LightTypeConfig +{ + int id; + QString name; +}; + +struct LightConfig +{ + int id; + QString name; + int lightTypeId; + int address; + QVector3D position; +}; + +struct LightProject +{ + std::vector lightTypes; + std::vector lights; +}; diff --git a/main-dev.qml b/main-dev.qml new file mode 100644 index 0000000..8315536 --- /dev/null +++ b/main-dev.qml @@ -0,0 +1,5 @@ +import QtQuick + +LightControlWindow { + visible: true +} diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..0baeb77 --- /dev/null +++ b/main.cpp @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#include + +#include "dmxcontroller.h" + +int main(int argc, char *argv[]) +{ + qSetMessagePattern(QStringLiteral("%{time dd.MM.yyyy HH:mm:ss.zzz} " + "[" + "%{if-debug}D%{endif}" + "%{if-info}I%{endif}" + "%{if-warning}W%{endif}" + "%{if-critical}C%{endif}" + "%{if-fatal}F%{endif}" + "] " + "%{function}(): " + "%{message}")); + + qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard")); + + QGuiApplication app{argc, argv}; + + QCommandLineParser parser; + parser.addHelpOption(); + parser.addVersionOption(); + + QCommandLineOption windowedOption { + QStringList{"w", "windowed"}, + QCoreApplication::translate("main", "Show in windowed (only dev)") + }; + parser.addOption(windowedOption); + + if (!parser.parse(app.arguments())) + { + qFatal("could not parse arguments!"); + return -1; + } + + const auto windowed = parser.isSet(windowedOption); + + DmxController controller{&app}; + if (!controller.start() && !windowed) + return -1; + + QQmlApplicationEngine engine; + + engine.rootContext()->setContextProperty("__controller", &controller); + + const QUrl url{windowed ? u"qrc:/lightcontrol/main-dev.qml"_qs : u"qrc:/lightcontrol/main.qml"_qs}; + QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, + &app, [url](QObject *obj, const QUrl &objUrl) { + if (!obj && url == objUrl) + QCoreApplication::exit(-1); + }, Qt::QueuedConnection); + engine.load(url); + + return app.exec(); +} diff --git a/main.qml b/main.qml new file mode 100644 index 0000000..f698687 --- /dev/null +++ b/main.qml @@ -0,0 +1,5 @@ +import QtQuick + +LightControlWindow { + Component.onCompleted: showFullScreen() +}