diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f44ebdb --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ + +CMakeLists.txt.user +Tksi125Monitor.pro.user diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..cf1bbd5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,92 @@ +cmake_minimum_required(VERSION 3.14) + +project(TksiMonitor VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Quick SerialPort LinguistTools) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Quick SerialPort LinguistTools) + +set(TS_FILES translations/TksiMonitor_ru_RU.ts) + +set(PROJECT_SOURCES + sources/main.cpp + sources/DataController.h + sources/DataController.cpp + resources/resources.qrc + qml/qml.qrc + ${TS_FILES} +) + +include_directories(sources) + +list(APPEND QML_IMPORT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/qml) +list(REMOVE_DUPLICATES QML_IMPORT_PATH) + +set(QML_IMPORT_PATH ${QML_IMPORT_PATH} + CACHE STRING "Qt Creator qml import paths" + FORCE +) + +if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) + qt_add_executable(TksiMonitor + MANUAL_FINALIZATION + ${PROJECT_SOURCES} + ) +# Define target properties for Android with Qt 6 as: +# set_property(TARGET TksiMonitor APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR +# ${CMAKE_CURRENT_SOURCE_DIR}/android) +# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation + + qt_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES}) +else() + if(ANDROID) + add_library(TksiMonitor SHARED + ${PROJECT_SOURCES} + ) +# Define properties for Android with Qt 5 after find_package() calls as: +# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") + else() + add_executable(TksiMonitor + ${PROJECT_SOURCES} + ) + endif() + + qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES}) +endif() + +target_link_libraries(TksiMonitor + PRIVATE + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Quick + Qt${QT_VERSION_MAJOR}::SerialPort +) + +# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. +# If you are developing for iOS or macOS you should consider setting an +# explicit, fixed bundle identifier manually though. +if(${QT_VERSION} VERSION_LESS 6.1.0) + set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.TksiMonitor) +endif() +set_target_properties(TksiMonitor PROPERTIES + ${BUNDLE_ID_OPTION} + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE TRUE + WIN32_EXECUTABLE TRUE +) + +include(GNUInstallDirs) +install(TARGETS TksiMonitor + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +if(QT_VERSION_MAJOR EQUAL 6) + qt_import_qml_plugins(TksiMonitor) + qt_finalize_executable(TksiMonitor) +endif() diff --git a/Tksi125Monitor.pro b/Tksi125Monitor.pro new file mode 100644 index 0000000..ae04439 --- /dev/null +++ b/Tksi125Monitor.pro @@ -0,0 +1,27 @@ +QT += quick quickcontrols2 serialport + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +HEADERS += sources/DataController.h + +SOURCES += \ + sources/main.cpp \ + sources/DataController.cpp + +RESOURCES += qml/qml.qrc \ + resources/resources.qrc + +# Additional import path used to resolve QML modules in Qt Creator's code model +QML_IMPORT_PATH = qml + +# Additional import path used to resolve QML modules just for Qt Quick Designer +QML_DESIGNER_IMPORT_PATH = + +# Rules for deployment. +linux { + QMAKE_POST_LINK += "cqtdeployer -bin $$OUT_PWD/$$TARGET -qmlDir $$PWD/qml force-clear zip deploySystem -extraLibs krb5,k5crypto,keyutils,md4c,udev,double-conversion" + QMAKE_POST_LINK += " ; cd $$PWD ; zip -r $$OUT_PWD/DistributionKit/Tksi125Monitor.zip settings.ini" + QMAKE_POST_LINK += " ; cp $$OUT_PWD/DistributionKit/Tksi125Monitor.zip $$clean_path($$PWD/../Tksi125MonitorLinux.zip)" +} diff --git a/deploy.txt b/deploy.txt new file mode 100644 index 0000000..7a96443 --- /dev/null +++ b/deploy.txt @@ -0,0 +1,3 @@ +# invoke this inside build directory +# works for Ubuntu 22.04 and 20.04 with system qt5 +cqtdeployer -bin ./TksiMonitor -qmlDir ../TksiMonitor/qml force-clear deploySystem -extraLibs krb5,k5crypto,keyutils,md4c,udev,double-conversion diff --git a/qml/Controls/DescriptionTextField.qml b/qml/Controls/DescriptionTextField.qml new file mode 100644 index 0000000..31275ef --- /dev/null +++ b/qml/Controls/DescriptionTextField.qml @@ -0,0 +1,28 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 + +import Controls 1.0 as CustomControls +import Palette 1.0 + +ColumnLayout { + id: root + spacing: 8 + + property string descriptionText + property string text + property string indicatorText + property bool readonly: false + + CustomControls.Label { + text: root.descriptionText + Layout.leftMargin: 24 + Layout.fillWidth: true + } + + CustomControls.TextField { + text: root.text + indicatorText: root.indicatorText + readOnly: root.readonly + Layout.fillWidth: true + } +} diff --git a/qml/Controls/GroupBox.qml b/qml/Controls/GroupBox.qml new file mode 100644 index 0000000..3c34bd1 --- /dev/null +++ b/qml/Controls/GroupBox.qml @@ -0,0 +1,34 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 as Controls +import QtQuick.Layouts 1.12 + +import Palette 1.0 + +Controls.GroupBox { + id: control + + leftPadding: 40 + rightPadding: 40 + topPadding: 56 + label.height + 48 + bottomPadding: 56 + + background: Rectangle { + color: Palette.controlBackgroundColor + radius: 32 + } + + label: Text { + id: label + x: control.leftPadding + y: 56 + width: control.availableWidth + + text: control.title + color: Palette.titleTextColor + elide: Text.ElideRight + + font.family: "Roboto" + font.pixelSize: 40 + font.weight: Font.Bold + } +} diff --git a/qml/Controls/IndicatorTextField.qml b/qml/Controls/IndicatorTextField.qml new file mode 100644 index 0000000..ed6b5f4 --- /dev/null +++ b/qml/Controls/IndicatorTextField.qml @@ -0,0 +1,29 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 as Controls + +import Palette 1.0 + +Controls.TextField { + id: control + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + topPadding: 10 + bottomPadding: 10 + leftPadding: 24 + rightPadding: 24 + + font.family: "Roboto" + font.pixelSize: 40 + font.weight: Font.Bold + + color: Palette.labelTextColor + property color backgroundColor: Palette.indicatorBackgroundColor + property int backgroundRadius: 24 + + background: Rectangle { + color: backgroundColor + radius: backgroundRadius + } +} diff --git a/qml/Controls/Label.qml b/qml/Controls/Label.qml new file mode 100644 index 0000000..11962b8 --- /dev/null +++ b/qml/Controls/Label.qml @@ -0,0 +1,7 @@ +import QtQuick 2.12 + +Text { + font.family: "Roboto" + font.pixelSize: 24 + font.weight: Font.Normal +} diff --git a/qml/Controls/Switch.qml b/qml/Controls/Switch.qml new file mode 100644 index 0000000..74eb63f --- /dev/null +++ b/qml/Controls/Switch.qml @@ -0,0 +1,73 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 as Controls +import QtQuick.Layouts 1.12 + +import Palette 1.0 + +Controls.Switch { + id: control + + implicitHeight: 128 + padding: 12 + + property string firstStateDescription + property string secondStateDescription + + indicator: Item { + anchors.fill: control + anchors.margins: control.padding + + RowLayout { + anchors.fill: parent + spacing: 0 + + Controls.Label { + id: firstLabel + + text: control.firstStateDescription + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + font.family: "Roboto" + font.pixelSize: 72 + font.weight: Font.Bold + + color: control.checked ? Palette.labelTextColor : Palette.alternativeLabelTextColor + Layout.preferredWidth: (parent.width -parent.spacing) / 2 + } + + Controls.Label { + id: secondLabel + + text: control.secondStateDescription + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + font.family: "Roboto" + font.pixelSize: 72 + font.weight: Font.Bold + + color: control.checked ? Palette.alternativeLabelTextColor : Palette.labelTextColor + Layout.preferredWidth: (parent.width -parent.spacing) / 2 + } + } + + Rectangle { + color: Palette.switchActiveBackgroundColor + radius: 24 + + width: control.checked ? secondLabel.width : firstLabel.width + height: parent.height + x: control.checked ? secondLabel.x : firstLabel.x + z: -1 + } + } + + contentItem: Item { + } + + background: Rectangle { + color: Palette.switchBackgroundColor + radius: 32 + } +} diff --git a/qml/Controls/TextField.qml b/qml/Controls/TextField.qml new file mode 100644 index 0000000..ae99f2d --- /dev/null +++ b/qml/Controls/TextField.qml @@ -0,0 +1,53 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 as Controls + +import Palette 1.0 + +Controls.TextField { + id: control + + implicitHeight: 108 + + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignVCenter + + topPadding: 12 + bottomPadding: 12 + leftPadding: 24 + rightPadding: 24 + Math.ceil(indicatorLabel.width) + (indicatorLabel.visible ? 12 : 0) + + font.family: "Roboto" + font.pixelSize: 72 + font.weight: Font.Bold + + color: Palette.labelTextColor + + property string indicatorText + + background: Rectangle { + radius: 32 + color: Palette.controlBackgroundColor + border.width: 1 + border.color: Palette.borderColor + + width: control.width + height: control.height + + Controls.Label { + id: indicatorLabel + visible: control.indicatorText.length > 0 + text: control.indicatorText + + anchors.right: parent.right + anchors.rightMargin: 24 + anchors.bottom: parent.bottom + anchors.bottomMargin: 20 + + font.family: "Roboto" + font.pixelSize: 40 + font.weight: Font.Normal + + color: Palette.descriptionTextColor + } + } +} diff --git a/qml/Controls/TristateSwitch.qml b/qml/Controls/TristateSwitch.qml new file mode 100644 index 0000000..20dc933 --- /dev/null +++ b/qml/Controls/TristateSwitch.qml @@ -0,0 +1,76 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 as Controls +import QtQuick.Layouts 1.12 + +import Palette 1.0 + +Controls.CheckBox { + id: control + + implicitHeight: 128 + padding: 12 + tristate: true + checkState: Qt.Unchecked + + property string firstStateDescription + property string secondStateDescription + + indicator: Item { + anchors.fill: control + anchors.margins: control.padding + + RowLayout { + anchors.fill: parent + spacing: 0 + + Controls.Label { + id: firstLabel + + text: control.firstStateDescription + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + font.family: "Roboto" + font.pixelSize: 72 + font.weight: Font.Bold + + color: control.checkState === Qt.PartiallyChecked ? Palette.alternativeLabelTextColor : Palette.labelTextColor + Layout.preferredWidth: (parent.width -parent.spacing) / 2 + } + + Controls.Label { + id: secondLabel + + text: control.secondStateDescription + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + font.family: "Roboto" + font.pixelSize: 72 + font.weight: Font.Bold + + color: control.checkState === Qt.Checked ? Palette.alternativeLabelTextColor : Palette.labelTextColor + Layout.preferredWidth: (parent.width -parent.spacing) / 2 + } + } + + Rectangle { + color: Palette.switchActiveBackgroundColor + radius: 24 + visible: control.checkState !== Qt.Unchecked + + width: control.checkState === Qt.Checked ? secondLabel.width : firstLabel.width + height: parent.height + x: control.checkState === Qt.Checked ? secondLabel.x : firstLabel.x + z: -1 + } + } + + contentItem: Item { + } + + background: Rectangle { + color: Palette.switchBackgroundColor + radius: 32 + } +} diff --git a/qml/Controls/qmldir b/qml/Controls/qmldir new file mode 100644 index 0000000..5006f75 --- /dev/null +++ b/qml/Controls/qmldir @@ -0,0 +1,8 @@ +module Controls +TextField 1.0 TextField.qml +IndicatorTextField 1.0 IndicatorTextField.qml +DescriptionTextField 1.0 DescriptionTextField.qml +Label 1.0 Label.qml +GroupBox 1.0 GroupBox.qml +Switch 1.0 Switch.qml +TristateSwitch 1.0 TristateSwitch.qml diff --git a/qml/Palette/Palette.qml b/qml/Palette/Palette.qml new file mode 100644 index 0000000..59b7357 --- /dev/null +++ b/qml/Palette/Palette.qml @@ -0,0 +1,23 @@ +pragma Singleton +import QtQuick 2.12 + +QtObject { + property color titleTextColor: "#232323" + property color labelTextColor: "#232323" + property color alternativeLabelTextColor: "#FFFFFF" + property color descriptionTextColor: "#757575" + + property color backgroundColor: "#EEE" + property color controlBackgroundColor: "#FCFCFC" + property color indicatorBackgroundColor: "#F0F4FF" + + property color switchBackgroundColor: "#F0F4FF" + property color switchActiveBackgroundColor: "#3069FE" + + property color borderColor: "#D0D0D0" + + property color informationColor: "#4C68ED" + property color invalidColor: "#A31C00" + property color goodColor: "#009352" + property color neutralColor: "#DFE0EB" +} diff --git a/qml/Palette/qmldir b/qml/Palette/qmldir new file mode 100644 index 0000000..1d90b4f --- /dev/null +++ b/qml/Palette/qmldir @@ -0,0 +1,2 @@ +module Palette +singleton Palette 1.0 Palette.qml \ No newline at end of file diff --git a/qml/main.qml b/qml/main.qml new file mode 100644 index 0000000..b078635 --- /dev/null +++ b/qml/main.qml @@ -0,0 +1,362 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 + +import Controls 1.0 as CustomControls +import Palette 1.0 + +import Tksi 1.0 + +ApplicationWindow { + id: root + width: 1920 + height: 1080 + visible: true + visibility: Qt.platform.os === "linux" ? "FullScreen" : "Windowed" + flags: Qt.platform.os === "linux" ? Qt.FramelessWindowHint | Qt.Window : Qt.Window + title: "ТКСИ 125 монитор" + + ColumnLayout { + anchors.leftMargin: 56 + anchors.rightMargin: 56 + anchors.topMargin: 80 + anchors.bottomMargin: 58 + anchors.fill: parent + + spacing: 72 + + RowLayout { + spacing: 96 + + ColumnLayout { + spacing: 16 + + CustomControls.Label { + text: "Статус модулей" + } + + RowLayout { + spacing: 16 + + CustomControls.IndicatorTextField { + text: DataController.firstStatus + horizontalAlignment: Text.AlignLeft + readOnly: true + Layout.preferredWidth: 160 + } + + CustomControls.IndicatorTextField { + text: DataController.secondStatus + readOnly: true + Layout.preferredWidth: 160 + } + + CustomControls.IndicatorTextField { + visible: DataController.firstStatus === "0x07" && DataController.secondStatus === "0x07" + text: "Готов к работе" + backgroundColor: "#C4FFCA" + backgroundRadius: 16 + font.pixelSize: 24 + font.weight: Font.Normal + leftPadding: 16 + rightPadding: 16 + readOnly: true + Layout.preferredWidth: 200 + } + } + + Item { + Layout.fillHeight: true + } + + Layout.fillHeight: true + } + + Item { + Layout.fillWidth: true + } + + CustomControls.DescriptionTextField { + visible: modeSwitch.checkState === Qt.PartiallyChecked + descriptionText: "Вход ПСН" + indicatorText: "В" + text: DataController.psnInputVoltage + readonly: true + Layout.alignment: Qt.AlignTop + Layout.fillWidth: false + Layout.preferredWidth: 320 + } + + CustomControls.DescriptionTextField { + descriptionText: "Сеть" + indicatorText: "В" + text: DataController.mainVoltage + readonly: true + Layout.alignment: Qt.AlignTop + Layout.fillWidth: false + Layout.preferredWidth: 320 + } + + ColumnLayout { + spacing: 16 + + CustomControls.Label { + id: dateLabel + text: "Статус модулей" + Layout.alignment: Qt.AlignCenter + } + + CustomControls.IndicatorTextField { + id: timeField + readOnly: true + Layout.alignment: Qt.AlignCenter + Layout.preferredWidth: 220 + } + + Item { + Layout.fillHeight: true + } + + Timer { + interval: 1000 + repeat: true + running: true + triggeredOnStart: true + + onTriggered: { + let date = new Date() + dateLabel.text = Qt.formatDate(date, "dd.MM.yyyy") + timeField.text = Qt.formatTime(date, "hh:mm:ss") + } + } + + Layout.fillHeight: true + } + + Layout.fillHeight: false + Layout.fillWidth: true + } + + CustomControls.TristateSwitch { + id: modeSwitch +// enabled: false // uncomment for non clickable + firstStateDescription: "ПСН" + secondStateDescription: "ИПБС" + checkState: DataController.ipbsMode === 2 ? Qt.Unchecked : DataController.ipbsMode === 1 ? Qt.Checked : Qt.PartiallyChecked + Layout.fillHeight: false + Layout.fillWidth: true + } + + RowLayout { + RowLayout { + spacing: 56 + visible: modeSwitch.checkState === Qt.PartiallyChecked + + CustomControls.GroupBox { + title: "АИН 1" + + ColumnLayout { + anchors.fill: parent + spacing: 32 + + CustomControls.DescriptionTextField { + descriptionText: "Напряжение" + indicatorText: "B" + text: DataController.ainFirstVoltage + readonly: true + Layout.fillWidth: true + } + + CustomControls.DescriptionTextField { + descriptionText: "Ток" + indicatorText: "А" + text: DataController.ainFirstCurrent + readonly: true + Layout.fillWidth: true + } + } + + Layout.preferredWidth: (parent.width - 2 * parent.spacing) / 3 + Layout.fillWidth: true + Layout.fillHeight: true + } + + CustomControls.GroupBox { + title: "АИН 2" + + ColumnLayout { + anchors.fill: parent + spacing: 32 + + CustomControls.DescriptionTextField { + descriptionText: "Напряжение" + indicatorText: "B" + text: DataController.ainSecondVoltage + readonly: true + Layout.fillWidth: true + } + + CustomControls.DescriptionTextField { + descriptionText: "Ток" + indicatorText: "А" + text: DataController.ainSecondCurrent + readonly: true + Layout.fillWidth: true + } + } + + Layout.preferredWidth: (parent.width - 2 * parent.spacing) / 3 + Layout.fillWidth: true + Layout.fillHeight: true + } + + CustomControls.GroupBox { + title: "АИН 3" + + ColumnLayout { + anchors.fill: parent + spacing: 32 + + CustomControls.DescriptionTextField { + descriptionText: "Напряжение" + indicatorText: "B" + text: DataController.ainThirdVoltage + readonly: true + Layout.fillWidth: true + } + + CustomControls.DescriptionTextField { + descriptionText: "Ток" + indicatorText: "А" + text: DataController.ainThirdCurrent + readonly: true + Layout.fillWidth: true + } + } + + Layout.preferredWidth: (parent.width - 2 * parent.spacing) / 3 + Layout.fillWidth: true + Layout.fillHeight: true + } + + Layout.fillWidth: true + Layout.fillHeight: true + } + + RowLayout { + spacing: 56 + visible: modeSwitch.checkState === Qt.Checked + + CustomControls.GroupBox { + title: "Выход 1 (Канал 1)" + + ColumnLayout { + anchors.fill: parent + spacing: 32 + + CustomControls.DescriptionTextField { + descriptionText: "Напряжение" + indicatorText: "B" + text: DataController.ipbsFirstVoltage + readonly: true + Layout.fillWidth: true + } + + CustomControls.DescriptionTextField { + descriptionText: "Ток" + indicatorText: "А" + text: DataController.ipbsFirstCurrent + readonly: true + Layout.fillWidth: true + } + } + + Layout.preferredWidth: (parent.width - 2 * parent.spacing) / 3 + Layout.fillWidth: true + Layout.fillHeight: true + } + + CustomControls.GroupBox { + title: "Выход 1 (Канал 2)" + + ColumnLayout { + anchors.fill: parent + spacing: 32 + + CustomControls.DescriptionTextField { + descriptionText: "Напряжение" + indicatorText: "B" + text: DataController.ipbsSecondVoltage + readonly: true + Layout.fillWidth: true + } + + CustomControls.DescriptionTextField { + descriptionText: "Ток" + indicatorText: "А" + text: DataController.ipbsSecondCurrent + readonly: true + Layout.fillWidth: true + } + } + + Layout.preferredWidth: (parent.width - 2 * parent.spacing) / 3 + Layout.fillWidth: true + Layout.fillHeight: true + } + + CustomControls.GroupBox { + title: "Выход 2" + + ColumnLayout { + anchors.fill: parent + spacing: 32 + + CustomControls.DescriptionTextField { + descriptionText: "Напряжение" + indicatorText: "B" + text: DataController.ipbsThirdVoltage + readonly: true + Layout.fillWidth: true + } + + CustomControls.DescriptionTextField { + descriptionText: "Ток" + indicatorText: "А" + text: DataController.ipbsThirdCurrent + readonly: true + Layout.fillWidth: true + } + } + + Layout.preferredWidth: (parent.width - 2 * parent.spacing) / 3 + Layout.fillWidth: true + Layout.fillHeight: true + } + + Layout.fillWidth: true + Layout.fillHeight: true + } + + Item { + visible: modeSwitch.checkState === Qt.Unchecked + Layout.fillWidth: true + Layout.fillHeight: true + } + + Layout.fillWidth: true + Layout.fillHeight: true + } + } + + MouseArea { + visible: Qt.platform.os === "linux" + anchors.fill: parent + enabled: false + cursorShape: Qt.BlankCursor + } + + background: Rectangle { + color: Palette.backgroundColor + } +} diff --git a/qml/qml.qrc b/qml/qml.qrc new file mode 100644 index 0000000..92f8050 --- /dev/null +++ b/qml/qml.qrc @@ -0,0 +1,15 @@ + + + main.qml + Controls/TextField.qml + Controls/qmldir + Palette/Palette.qml + Palette/qmldir + Controls/GroupBox.qml + Controls/Label.qml + Controls/IndicatorTextField.qml + Controls/Switch.qml + Controls/DescriptionTextField.qml + Controls/TristateSwitch.qml + + diff --git a/resources/fonts/Roboto/LICENSE.txt b/resources/fonts/Roboto/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/resources/fonts/Roboto/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/resources/fonts/Roboto/Roboto-Black.ttf b/resources/fonts/Roboto/Roboto-Black.ttf new file mode 100644 index 0000000..0112e7d Binary files /dev/null and b/resources/fonts/Roboto/Roboto-Black.ttf differ diff --git a/resources/fonts/Roboto/Roboto-BlackItalic.ttf b/resources/fonts/Roboto/Roboto-BlackItalic.ttf new file mode 100644 index 0000000..b2c6aca Binary files /dev/null and b/resources/fonts/Roboto/Roboto-BlackItalic.ttf differ diff --git a/resources/fonts/Roboto/Roboto-Bold.ttf b/resources/fonts/Roboto/Roboto-Bold.ttf new file mode 100644 index 0000000..43da14d Binary files /dev/null and b/resources/fonts/Roboto/Roboto-Bold.ttf differ diff --git a/resources/fonts/Roboto/Roboto-BoldItalic.ttf b/resources/fonts/Roboto/Roboto-BoldItalic.ttf new file mode 100644 index 0000000..bcfdab4 Binary files /dev/null and b/resources/fonts/Roboto/Roboto-BoldItalic.ttf differ diff --git a/resources/fonts/Roboto/Roboto-Italic.ttf b/resources/fonts/Roboto/Roboto-Italic.ttf new file mode 100644 index 0000000..1b5eaa3 Binary files /dev/null and b/resources/fonts/Roboto/Roboto-Italic.ttf differ diff --git a/resources/fonts/Roboto/Roboto-Light.ttf b/resources/fonts/Roboto/Roboto-Light.ttf new file mode 100644 index 0000000..e7307e7 Binary files /dev/null and b/resources/fonts/Roboto/Roboto-Light.ttf differ diff --git a/resources/fonts/Roboto/Roboto-LightItalic.ttf b/resources/fonts/Roboto/Roboto-LightItalic.ttf new file mode 100644 index 0000000..2d277af Binary files /dev/null and b/resources/fonts/Roboto/Roboto-LightItalic.ttf differ diff --git a/resources/fonts/Roboto/Roboto-Medium.ttf b/resources/fonts/Roboto/Roboto-Medium.ttf new file mode 100644 index 0000000..ac0f908 Binary files /dev/null and b/resources/fonts/Roboto/Roboto-Medium.ttf differ diff --git a/resources/fonts/Roboto/Roboto-MediumItalic.ttf b/resources/fonts/Roboto/Roboto-MediumItalic.ttf new file mode 100644 index 0000000..fc36a47 Binary files /dev/null and b/resources/fonts/Roboto/Roboto-MediumItalic.ttf differ diff --git a/resources/fonts/Roboto/Roboto-Regular.ttf b/resources/fonts/Roboto/Roboto-Regular.ttf new file mode 100644 index 0000000..ddf4bfa Binary files /dev/null and b/resources/fonts/Roboto/Roboto-Regular.ttf differ diff --git a/resources/fonts/Roboto/Roboto-Thin.ttf b/resources/fonts/Roboto/Roboto-Thin.ttf new file mode 100644 index 0000000..2e0dee6 Binary files /dev/null and b/resources/fonts/Roboto/Roboto-Thin.ttf differ diff --git a/resources/fonts/Roboto/Roboto-ThinItalic.ttf b/resources/fonts/Roboto/Roboto-ThinItalic.ttf new file mode 100644 index 0000000..084f9c0 Binary files /dev/null and b/resources/fonts/Roboto/Roboto-ThinItalic.ttf differ diff --git a/resources/resources.qrc b/resources/resources.qrc new file mode 100644 index 0000000..df94610 --- /dev/null +++ b/resources/resources.qrc @@ -0,0 +1,16 @@ + + + fonts/Roboto/Roboto-Black.ttf + fonts/Roboto/Roboto-BlackItalic.ttf + fonts/Roboto/Roboto-Bold.ttf + fonts/Roboto/Roboto-BoldItalic.ttf + fonts/Roboto/Roboto-Italic.ttf + fonts/Roboto/Roboto-Light.ttf + fonts/Roboto/Roboto-LightItalic.ttf + fonts/Roboto/Roboto-Medium.ttf + fonts/Roboto/Roboto-MediumItalic.ttf + fonts/Roboto/Roboto-Regular.ttf + fonts/Roboto/Roboto-Thin.ttf + fonts/Roboto/Roboto-ThinItalic.ttf + + diff --git a/settings.ini b/settings.ini new file mode 100644 index 0000000..529aaf4 --- /dev/null +++ b/settings.ini @@ -0,0 +1,7 @@ +[General] +name=/dev/ttyS0 +;baudRate=9600 +;dataBits=8 +;parity=0 +;stopBits=1 +;flowControl=0 diff --git a/sources/DataController.cpp b/sources/DataController.cpp new file mode 100644 index 0000000..0c0c8c4 --- /dev/null +++ b/sources/DataController.cpp @@ -0,0 +1,287 @@ +#include "DataController.h" + +#include +#include +#include +#include +#include +#include + +#include + +DataController::DataController(QObject* parent) + : QObject{parent} + , serialPort_(new QSerialPort) +{ + connect(serialPort_.data(), &QSerialPort::readyRead, this, &DataController::serialReadyRead); + connect(serialPort_.data(), &QSerialPort::errorOccurred, this, &DataController::serialError); + + // for correct qApp->exit(); + QTimer::singleShot(0, this, [this]{ openSerialPort(); }); +} + +DataController::~DataController() +{ +} + +QObject* DataController::qmlInstance(QQmlEngine* /*engine*/, QJSEngine* /*scriptEngine*/) +{ + return new DataController; +} + +void DataController::serialReadyRead() +{ + const auto data = serialPort_->readAll(); + qDebug() << "Serial data received (HEX):" << QDateTime::currentDateTime() << data.toHex(); + + serialData_ += data; + parseSerialData(); +} + +void DataController::serialError() +{ + const auto error = serialPort_->error(); + if (error == QSerialPort::NoError) + { + return; + } + + qCritical() << "A serial port error has occurred. Port:" << serialPort_->portName() << "Error:" << serialPort_->errorString(); + qApp->exit(1); +} + +QVariantMap DataController::readSettings() +{ + QSettings settings("settings.ini", QSettings::IniFormat); + + const auto keys = settings.allKeys(); + if (keys.isEmpty()) + { + qCritical() << "File settings.ini is empty. Please put the file next to the executable file."; + qApp->exit(1); + } + + + QVariantMap settingsMap; + for (const auto& key: keys) + { + settingsMap[key] = settings.value(key); + } + + return settingsMap; +} + +void DataController::openSerialPort() +{ + const auto settings = readSettings(); + if (settings.isEmpty()) + { + return; + } + + const auto portName = settings["name"].toString(); + + // for testing + if (portName == "fake") + { + mockSerialData(); + parseSerialData(); + return; + } + + serialPort_->setPortName(portName); + + if (settings.contains("baudRate")) + { + serialPort_->setBaudRate(settings["baudRate"].toInt()); + } + + if (settings.contains("dataBits")) + { + serialPort_->setDataBits((QSerialPort::DataBits)settings["dataBits"].toInt()); + } + + if (settings.contains("parity")) + { + serialPort_->setParity((QSerialPort::Parity)settings["parity"].toInt()); + } + + if (settings.contains("stopBits")) + { + serialPort_->setStopBits((QSerialPort::StopBits)settings["stopBits"].toInt()); + } + + if (settings.contains("flowControl")) + { + serialPort_->setFlowControl((QSerialPort::FlowControl)settings["flowControl"].toInt()); + } + + if (!serialPort_->open(QIODevice::ReadOnly)) + { + qCritical() << "Can't open serial port. Port:" << serialPort_->portName() << "Error:" << serialPort_->errorString(); + qApp->exit(1); + } +} + +void DataController::parseSerialData() +{ + const char* startBytes = "\xD0\x0D"; + const auto startPosition = serialData_.indexOf(startBytes); + if (startPosition == -1) + { + serialData_.clear(); + return; + } + + if (startPosition > 0) + { + serialData_ = serialData_.mid(startPosition); + } + + if (serialData_.size() < 35) + { + return; + } + + auto extractInt = [](const QByteArray& array, int start, int length) -> uint32_t + { + uint32_t result = 0; + if (length > 4) + { + return result; + } + + // reversed byte order + for (int i = 0; i < length; ++i) + { + memcpy(reinterpret_cast(&result) + i, array.data() + start + length - i - 1, 1); + } + + // same byte order +// memcpy(&result, array.data() + start, length); + return result; + }; + + auto statusToString = [](uint32_t status) -> QString + { + auto hexStatus = "0x" + QString("%1").arg(status, 2, 16, QLatin1Char('0')).toUpper(); + if (hexStatus.compare("0xFF", Qt::CaseInsensitive) == 0) + { + hexStatus = " — "; + } + + return hexStatus; + }; + + auto shortIntToString = [](uint16_t parameter) -> QString + { + return parameter == std::numeric_limits::max() ? " — " : QString::number(parameter); + }; + + auto shortDoubleToString = [](uint16_t parameter) -> QString + { + return parameter == std::numeric_limits::max() ? " — " : QString::number(parameter / 10.0, 'f', 1); + }; + + const auto package = serialData_.mid(0, 35); + serialData_ = serialData_.mid(35); + + quint16 calculatedChecksum = 0; + for (int i = 0; i < 33; ++i) + { + calculatedChecksum += static_cast(package.at(i)); + } + + const auto checksum = extractInt(package, 33, 2); + if (calculatedChecksum != checksum) + { + qWarning() << "incorrect checksum"; + return; + } + + setProperty("ipbsFirstVoltage", shortDoubleToString(extractInt(package, 2, 2))); + setProperty("ipbsFirstCurrent", shortDoubleToString(extractInt(package, 4, 2))); + + setProperty("ipbsSecondVoltage", shortDoubleToString(extractInt(package, 6, 2))); + setProperty("ipbsSecondCurrent", shortDoubleToString(extractInt(package, 8, 2))); + + setProperty("ipbsThirdVoltage", shortDoubleToString(extractInt(package, 10, 2))); + setProperty("ipbsThirdCurrent", shortDoubleToString(extractInt(package, 12, 2))); + + setProperty("mainVoltage", shortIntToString(extractInt(package, 14, 2))); + setProperty("ipbsMode", extractInt(package, 16, 1)); + + setProperty("firstStatus", statusToString(extractInt(package, 17, 1))); + setProperty("secondStatus", statusToString(extractInt(package, 18, 1))); + + setProperty("ainFirstVoltage", shortIntToString(extractInt(package, 19, 2))); + setProperty("ainFirstCurrent", shortDoubleToString(extractInt(package, 21, 2))); + + setProperty("ainSecondVoltage", shortIntToString(extractInt(package, 23, 2))); + setProperty("ainSecondCurrent", shortDoubleToString(extractInt(package, 25, 2))); + + setProperty("ainThirdVoltage", shortIntToString(extractInt(package, 27, 2))); + setProperty("ainThirdCurrent", shortDoubleToString(extractInt(package, 29, 2))); + + setProperty("psnInputVoltage", shortIntToString(extractInt(package, 31, 2))); +} + +void DataController::mockSerialData() +{ + serialData_.clear(); + + serialData_.append((char)0xD0); + serialData_.append((char)0x0D); + + uint16_t voltage1 = qToBigEndian((uint16_t)1102); + uint16_t current1 = qToBigEndian((uint16_t)955); + serialData_.append(reinterpret_cast(&voltage1), 2); + serialData_.append(reinterpret_cast(¤t1), 2); + + uint16_t voltage2 = qToBigEndian((uint16_t)1097); + uint16_t current2 = qToBigEndian((uint16_t)831); + serialData_.append(reinterpret_cast(&voltage2), 2); + serialData_.append(reinterpret_cast(¤t2), 2); + + uint16_t voltage3 = qToBigEndian((uint16_t)1222); + uint16_t current3 = qToBigEndian((uint16_t)353); + serialData_.append(reinterpret_cast(&voltage3), 2); + serialData_.append(reinterpret_cast(¤t3), 2); + + uint16_t mainVoltage = qToBigEndian((uint16_t)384); + char mode = 0; + serialData_.append(reinterpret_cast(&mainVoltage), 2); + serialData_.append(mode); + + char status1 = 0x0; + char status2 = 0x0; + serialData_.append(status1); + serialData_.append(status2); + + uint16_t voltage4 = qToBigEndian((uint16_t)380); + uint16_t current4 = qToBigEndian((uint16_t)535); + serialData_.append(reinterpret_cast(&voltage4), 2); + serialData_.append(reinterpret_cast(¤t4), 2); + + uint16_t voltage5 = qToBigEndian((uint16_t)381); + uint16_t current5 = qToBigEndian((uint16_t)527); + serialData_.append(reinterpret_cast(&voltage5), 2); + serialData_.append(reinterpret_cast(¤t5), 2); + + uint16_t voltage6 = qToBigEndian((uint16_t)383); + uint16_t current6 = qToBigEndian((uint16_t)729); + serialData_.append(reinterpret_cast(&voltage6), 2); + serialData_.append(reinterpret_cast(¤t6), 2); + + uint16_t inputVoltage = qToBigEndian((uint16_t)3010); + serialData_.append(reinterpret_cast(&inputVoltage), 2); + + quint16 checksum = 0; + for (int i = 0; i < serialData_.size(); ++i) + { + checksum += static_cast(serialData_.at(i)); + } + + checksum = qToBigEndian(checksum); + serialData_.append(reinterpret_cast(&checksum), 2); +} diff --git a/sources/DataController.h b/sources/DataController.h new file mode 100644 index 0000000..d8cf018 --- /dev/null +++ b/sources/DataController.h @@ -0,0 +1,97 @@ +#ifndef DATACONTROLLER_H +#define DATACONTROLLER_H + +#include + +class QQmlEngine; +class QJSEngine; +class QSerialPort; + +class DataController : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString ipbsFirstVoltage MEMBER ipbsFirstVoltage_ NOTIFY ipbsFirstVoltageChanged) + Q_PROPERTY(QString ipbsFirstCurrent MEMBER ipbsFirstCurrent_ NOTIFY ipbsFirstCurrentChanged) + + Q_PROPERTY(QString ipbsSecondVoltage MEMBER ipbsSecondVoltage_ NOTIFY ipbsSecondVoltageChanged) + Q_PROPERTY(QString ipbsSecondCurrent MEMBER ipbsSecondCurrent_ NOTIFY ipbsSecondCurrentChanged) + + Q_PROPERTY(QString ipbsThirdVoltage MEMBER ipbsThirdVoltage_ NOTIFY ipbsThirdVoltageChanged) + Q_PROPERTY(QString ipbsThirdCurrent MEMBER ipbsThirdCurrent_ NOTIFY ipbsThirdCurrentChanged) + + Q_PROPERTY(QString mainVoltage MEMBER mainVoltage_ NOTIFY mainVoltageChanged) + Q_PROPERTY(int ipbsMode MEMBER ipbsMode_ NOTIFY ipbsModeChanged) + + Q_PROPERTY(QString firstStatus MEMBER firstStatus_ NOTIFY firstStatusChanged) + Q_PROPERTY(QString secondStatus MEMBER secondStatus_ NOTIFY secondStatusChanged) + + Q_PROPERTY(QString ainFirstVoltage MEMBER ainFirstVoltage_ NOTIFY ainFirstVoltageChanged) + Q_PROPERTY(QString ainFirstCurrent MEMBER ainFirstCurrent_ NOTIFY ainFirstCurrentChanged) + + Q_PROPERTY(QString ainSecondVoltage MEMBER ainSecondVoltage_ NOTIFY ainSecondVoltageChanged) + Q_PROPERTY(QString ainSecondCurrent MEMBER ainSecondCurrent_ NOTIFY ainSecondCurrentChanged) + + Q_PROPERTY(QString ainThirdVoltage MEMBER ainThirdVoltage_ NOTIFY ainThirdVoltageChanged) + Q_PROPERTY(QString ainThirdCurrent MEMBER ainThirdCurrent_ NOTIFY ainThirdCurrentChanged) + + Q_PROPERTY(QString psnInputVoltage MEMBER psnInputVoltage_ NOTIFY psnInputVoltageChanged) + +public: + explicit DataController(QObject* parent = nullptr); + ~DataController(); + + static QObject* qmlInstance(QQmlEngine* engine, QJSEngine* scriptEngine); + +signals: + void ipbsFirstVoltageChanged(); + void ipbsFirstCurrentChanged(); + void ipbsSecondVoltageChanged(); + void ipbsSecondCurrentChanged(); + void ipbsThirdVoltageChanged(); + void ipbsThirdCurrentChanged(); + void mainVoltageChanged(); + void ipbsModeChanged(); + void firstStatusChanged(); + void secondStatusChanged(); + void ainFirstVoltageChanged(); + void ainFirstCurrentChanged(); + void ainSecondVoltageChanged(); + void ainSecondCurrentChanged(); + void ainThirdVoltageChanged(); + void ainThirdCurrentChanged(); + void psnInputVoltageChanged(); + +private slots: + void serialReadyRead(); + void serialError(); + +private: + QVariantMap readSettings(); + void openSerialPort(); + void parseSerialData(); + void mockSerialData(); + + QString ipbsFirstVoltage_; + QString ipbsFirstCurrent_; + QString ipbsSecondVoltage_; + QString ipbsSecondCurrent_; + QString ipbsThirdVoltage_; + QString ipbsThirdCurrent_; + QString mainVoltage_; + int ipbsMode_ = 0; + QString firstStatus_ = " — "; + QString secondStatus_ = " — "; + QString ainFirstVoltage_; + QString ainFirstCurrent_; + QString ainSecondVoltage_; + QString ainSecondCurrent_; + QString ainThirdVoltage_; + QString ainThirdCurrent_; + QString psnInputVoltage_; + + QByteArray serialData_; + QScopedPointer serialPort_; +}; + +#endif // DATACONTROLLER_H diff --git a/sources/main.cpp b/sources/main.cpp new file mode 100644 index 0000000..090bdbf --- /dev/null +++ b/sources/main.cpp @@ -0,0 +1,72 @@ +#include +#include +#include + +#include +#include +#include + +#include "DataController.h" + +int main(int argc, char *argv[]) +{ + //#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + // QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + //#endif + + QGuiApplication app(argc, argv); + + QCoreApplication::setOrganizationName("Tksi"); + QCoreApplication::setOrganizationDomain("Tksi.rus"); + QCoreApplication::setApplicationName("Tksi monitor"); + + QFontDatabase::addApplicationFont(":/fonts/Roboto/Roboto-Black.ttf"); + QFontDatabase::addApplicationFont(":/fonts/Roboto/Roboto-BlackItalic.ttf"); + QFontDatabase::addApplicationFont(":/fonts/Roboto/Roboto-Bold.ttf"); + QFontDatabase::addApplicationFont(":/fonts/Roboto/Roboto-BoldItalic.ttf"); + QFontDatabase::addApplicationFont(":/fonts/Roboto/Roboto-Italic.ttf"); + QFontDatabase::addApplicationFont(":/fonts/Roboto/Roboto-Light.ttf"); + QFontDatabase::addApplicationFont(":/fonts/Roboto/Roboto-LightItalic.ttf"); + QFontDatabase::addApplicationFont(":/fonts/Roboto/Roboto-Medium.ttf"); + QFontDatabase::addApplicationFont(":/fonts/Roboto/Roboto-MediumItalic.ttf"); + QFontDatabase::addApplicationFont(":/fonts/Roboto/Roboto-Regular.ttf"); + QFontDatabase::addApplicationFont(":/fonts/Roboto/Roboto-Thin.ttf"); + QFontDatabase::addApplicationFont(":/fonts/Roboto/Roboto-ThinItalic.ttf"); + + QTranslator translator; + const auto uiLanguages = QLocale::system().uiLanguages(); + for (const auto& locale : uiLanguages) + { + const auto baseName = "TksiMonitor_" + QLocale(locale).name(); + if (translator.load(":/i18n/" + baseName)) + { + app.installTranslator(&translator); + break; + } + } + + QQmlApplicationEngine engine; + + qmlRegisterSingletonType("Tksi", 1, 0, "DataController", &DataController::qmlInstance); + + auto isDebug = false; +#ifdef QT_DEBUG + isDebug = true; +#endif + engine.rootContext()->setContextProperty("isDebug", isDebug); + + const QUrl url(QStringLiteral("qrc:/main.qml")); + QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, + [url](QObject *obj, const QUrl &objUrl) + { + if (!obj && url == objUrl) + { + QCoreApplication::exit(-1); + } + }, Qt::QueuedConnection); + + engine.addImportPath(QStringLiteral("qrc:/")); + engine.load(url); + + return app.exec(); +} diff --git a/translations/TksiMonitor_ru_RU.ts b/translations/TksiMonitor_ru_RU.ts new file mode 100644 index 0000000..743f57f --- /dev/null +++ b/translations/TksiMonitor_ru_RU.ts @@ -0,0 +1,3 @@ + + +