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() +}