Implemented basic functionality

This commit is contained in:
2023-02-15 01:12:19 +01:00
parent e492cf82b9
commit 64cff3ce78
21 changed files with 1441 additions and 0 deletions

49
CMakeLists.txt Normal file
View File

@ -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})

View File

@ -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
}
}
}
}

67
DevicesSettingsPage.qml Normal file
View File

@ -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
}
}
}
}

72
EditableListView.qml Normal file
View File

@ -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
}
}

71
HomePage.qml Normal file
View File

@ -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
}
}
}

189
LightControlWindow.qml Normal file
View File

@ -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
}
}
}
]
}
}

View File

@ -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
```

47
SettingsPage.qml Normal file
View File

@ -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 {
}
}
}
}
}

280
devicesmodel.cpp Normal file
View File

@ -0,0 +1,280 @@
#include "devicesmodel.h"
#include <QCoreApplication>
#include <QQmlEngine>
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<int, QVariant> 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<int, QByteArray> 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<QVector3D>();
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<DevicesModel>("com.büro", 1, 0, "DevicesModel");
}
}
Q_COREAPP_STARTUP_FUNCTION(registerDenShit)

35
devicesmodel.h Normal file
View File

@ -0,0 +1,35 @@
#pragma once
#include <QAbstractItemModel>
#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<int, QVariant> itemData(const QModelIndex &index) const override;
QHash<int, QByteArray> 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{};
};

241
devicetypesmodel.cpp Normal file
View File

@ -0,0 +1,241 @@
#include "devicetypesmodel.h"
#include <QCoreApplication>
#include <QQmlEngine>
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<int, QVariant> 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<int, QByteArray> 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<DeviceTypesModel>("com.büro", 1, 0, "DeviceTypesModel");
}
}
Q_COREAPP_STARTUP_FUNCTION(registerDenShit)

35
devicetypesmodel.h Normal file
View File

@ -0,0 +1,35 @@
#pragma once
#include <QAbstractItemModel>
#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<int, QVariant> itemData(const QModelIndex &index) const override;
QHash<int, QByteArray> 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{};
};

109
dmxcontroller.cpp Normal file
View File

@ -0,0 +1,109 @@
#include "dmxcontroller.h"
#include <unistd.h>
#include <sys/ioctl.h>
#include <QDebug>
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;
}
}

44
dmxcontroller.h Normal file
View File

@ -0,0 +1,44 @@
#pragma once
#include <QObject>
#include <QSerialPort>
#include <QDateTime>
#include <QReadWriteLock>
#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;
};

15
dmxcontrollerthread.cpp Normal file
View File

@ -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();
}

18
dmxcontrollerthread.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <QThread>
class DmxController;
class DmxControllerThread : public QThread
{
Q_OBJECT
public:
explicit DmxControllerThread(DmxController &controller);
void run() override;
private:
DmxController &m_controller;
};

1
lightproject.cpp Normal file
View File

@ -0,0 +1 @@
#include "lightproject.h"

27
lightproject.h Normal file
View File

@ -0,0 +1,27 @@
#pragma once
#include <vector>
#include <QString>
#include <QVector3D>
struct LightTypeConfig
{
int id;
QString name;
};
struct LightConfig
{
int id;
QString name;
int lightTypeId;
int address;
QVector3D position;
};
struct LightProject
{
std::vector<LightTypeConfig> lightTypes;
std::vector<LightConfig> lights;
};

5
main-dev.qml Normal file
View File

@ -0,0 +1,5 @@
import QtQuick
LightControlWindow {
visible: true
}

61
main.cpp Normal file
View File

@ -0,0 +1,61 @@
#include <QGuiApplication>
#include <QCommandLineParser>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QDebug>
#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();
}

5
main.qml Normal file
View File

@ -0,0 +1,5 @@
import QtQuick
LightControlWindow {
Component.onCompleted: showFullScreen()
}