diff --git a/at_Qt_Brew/BrewMonitor/About.qml b/at_Qt_Brew/BrewMonitor/About.qml new file mode 100644 index 0000000..f24d7d1 --- /dev/null +++ b/at_Qt_Brew/BrewMonitor/About.qml @@ -0,0 +1,63 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Pdf + +Pane { + id: root + width: Constants.width + height: Constants.height - 50 + background: Rectangle { + id: back + // color: "#002125" + } + + PdfDocument { + id: doc + source: "Images/CES-slides.pdf" + } + + PdfMultiPageView { + id: view + anchors.fill: parent + document: doc + //shrink pdf to fit in parent + // next page when bottom half is tapped + MouseArea { + anchors.right: parent.right + anchors.left: parent.left + anchors.top: parent.verticalCenter + anchors.bottom: parent.bottom + onClicked: { + var cur = view.currentPage + cur < 5 ? view.goToPage(cur + 1) : {} + //set back.color to a randomized color + back.color = Qt.rgba(Math.random(), Math.random(), + Math.random(), 1) + } + } + + //prev page when top half is tapped + MouseArea { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.verticalCenter + onClicked: { + var cur = view.currentPage + cur ? view.goToPage(cur - 1) : {} + } + } + + Component.onCompleted: { + view.scaleToWidth(parent.width, parent.height) + //workaround for visual bug in pdf initial display position + view.goToPage(0) + } + } + + Component.onCompleted: { + //workaround for visual bug in pdf initial display position + view.goToPage(3) + } +} diff --git a/at_Qt_Brew/BrewMonitor/AppSettings.qml b/at_Qt_Brew/BrewMonitor/AppSettings.qml new file mode 100644 index 0000000..c023ea7 --- /dev/null +++ b/at_Qt_Brew/BrewMonitor/AppSettings.qml @@ -0,0 +1,13 @@ +pragma Singleton + +import QtQuick +import QtCore + +Settings { + property bool isDarkTheme: Qt.styleHints.colorScheme === Qt.Dark + // property color backgroundColor: AppSettings.isDarkTheme ? "#000000" : "#EFFCF6" + // property color accentColor: "purple" + // property color primaryTextColor: "#FFFFFF" + // property color accentTextColor: AppSettings.isDarkTheme ? "#D9D9D9" : "#898989" + // property color iconColor: AppSettings.isDarkTheme ? "#D9D9D9" : "#00414A" +} diff --git a/at_Qt_Brew/BrewMonitor/Beer.qml b/at_Qt_Brew/BrewMonitor/Beer.qml new file mode 100644 index 0000000..c5f86d6 --- /dev/null +++ b/at_Qt_Brew/BrewMonitor/Beer.qml @@ -0,0 +1,714 @@ + +/* +This is a UI file (.ui.qml) that is intended to be edited in Qt Design Studio only. +It is supposed to be strictly declarative and only uses a subset of QML. If you edit +this file manually, you might introduce QML code that is not supported by Qt Design Studio. +Check out https://doc.qt.io/qtcreator/creator-quick-ui-forms.html for details on .ui.qml files. +*/ +import QtQuick +import QtQuick.Controls +import Qt5Compat.GraphicalEffects +import QtQuick.Layouts +import BeerTap + +//this is a design studio file that should be refactored +Page { + id: pane + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + width: Constants.width + height: Constants.height - 50 + property color tileColor: "black" + property color glowColor: "#55ff55" //add orange glow in light mode + property real tileGlowRadius: 5 + property int beer1Count: 5 + property int beer2Count: 14 + property int selectedBeer: -1 + property int sizeSelected: 0 + property int displayCalories: 0 + property real displayABV: 0 + property int displayCount: 0 + property real displayFat: 0 + property real displayCholesterol: 0 + property real displaySodium: 0 + property real displayCarbs: 0 + property real displayProtein: 0 + + Behavior on displayCalories { + NumberAnimation { + duration: 2000 + easing.type: Easing.InOutQuad + } + } + Behavior on displayABV { + NumberAnimation { + duration: 2000 + easing.type: Easing.InOutQuad + } + } + Behavior on displayCount { + NumberAnimation { + duration: 2000 + easing.type: Easing.InOutQuad + } + } + Behavior on displayFat { + NumberAnimation { + duration: 2000 + easing.type: Easing.InOutQuad + } + } + Behavior on displayCholesterol { + NumberAnimation { + duration: 2000 + easing.type: Easing.InOutQuad + } + } + Behavior on displaySodium { + NumberAnimation { + duration: 2000 + easing.type: Easing.InOutQuad + } + } + Behavior on displayCarbs { + NumberAnimation { + duration: 2000 + easing.type: Easing.InOutQuad + } + } + Behavior on displayProtein { + NumberAnimation { + duration: 2000 + easing.type: Easing.InOutQuad + } + } + + Rectangle { + id: background + color: "#002125" + anchors.fill: parent + } + + RectangularGlow { + id: nutritionInfoGlow + cached: false + anchors.fill: nutritionInfo + glowRadius: tileGlowRadius + spread: 0.1 + color: glowColor + cornerRadius: nutritionInfo.radius + glowRadius + } + + SequentialAnimation { + id: tileGlowAnimation + PropertyAnimation { + id: tileGlowRadiusAnimationBrighten + target: pane // Target the glow component + property: "tileGlowRadius" // The property to animate + from: 0 // Starting glow radius + to: 5 // Ending glow radius + duration: 3500 // Duration of the animation in milliseconds + // loops: Animation.Infinite // Loop indefinitely + easing.type: Easing.InOutSine // Smooth easing for a more natural effect + + running: true // Start the animation immediately + } + PropertyAnimation { + id: tileGlowRadiusAnimationDim + target: pane // Target the glow component + property: "tileGlowRadius" // The property to animate + from: 5 // Starting glow radius + to: 0 // Ending glow radius + duration: 3500 // Duration of the animation in milliseconds + /* loops: Animation.Infinite*/ // Loop indefinitely + easing.type: Easing.InOutSine // Smooth easing for a more natural effect + + running: true // Start the animation immediately + } + loops: Animation.Infinite + running: true + } + + Rectangle { + id: nutritionInfo + // calories + x: 40 + y: 240 + width: 150 + height: 211 + color: tileColor + radius: 25 + + Text { + id: nutritionText + text: "Nutrition Info" + font.pointSize: 14 + width: contentWidth + anchors.horizontalCenter: parent.horizontalCenter + color: "#FFFFFF" + } + + GridLayout { + anchors.top: nutritionText.bottom + anchors.bottom: parent.bottom + columns: 2 + rows: 5 + columnSpacing: 20 + rowSpacing: 0 + + Text { + text: "Total Fat" + color: "#FFFFFF" + Layout.leftMargin: 15 + } + Text { + text: Math.round(displayFat * 10) / 10 + "g" + color: "#FFFFFF" + } + + Text { + text: "Cholesterol" + color: "#FFFFFF" + Layout.leftMargin: 15 + } + Text { + text: Math.round(displayCholesterol * 10) / 10 + "g" + color: "#FFFFFF" + } + + Text { + text: "Sodium" + color: "#FFFFFF" + Layout.leftMargin: 15 + } + Text { + text: Math.round(displaySodium * 10) / 10 + "g" + color: "#FFFFFF" + } + + Text { + text: "Total Carbs" + color: "#FFFFFF" + Layout.leftMargin: 15 + } + Text { + text: Math.round(displayCarbs * 10) / 10 + "g" + color: "#FFFFFF" + } + + Text { + text: "Protein" + color: "#FFFFFF" + Layout.leftMargin: 15 + } + Text { + text: Math.round(displayProtein * 10) / 10 + "g" + color: "#FFFFFF" + } + } + } + + RectangularGlow { + id: beer1Glow + anchors.fill: beer1 + glowRadius: tileGlowRadius + spread: 0.1 + color: "white" + cornerRadius: beer1.radius + glowRadius + visible: selectedBeer === 0 || selectedBeer === -1 + } + + RoundButton { + id: beer1 + radius: 25 + anchors.centerIn: parent + width: 180 + height: 260 + anchors.verticalCenterOffset: -100 + anchors.horizontalCenterOffset: -125 + // opacity: selectedBeer === 0 ? 1 : 0.5 + property int calories: 173 + property real abv: 4.8 + property real fat: 0 + property real cholesterol: 0 + property real sodium: 0.011 + property real carbs: 11 + property real protein: 1 + + Image { + id: beer1Image + // anchors.fill:parent + source: "Images/Budweiser.png" + // sourceSize.width: parent.width + // sourceSize.height: 100 + anchors.centerIn: parent + anchors.verticalCenterOffset: 30 + scale: .175 + } + Glow { + anchors.fill: beer1Image + source: beer1Image + radius: 25 + spread: 0.2 + color: "white" + scale: beer1Image.scale + } + + Text { + text: "Budweiser" + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + font.pointSize: 16 + color: "#FFFFFF" + } + + background: Rectangle { + implicitWidth: parent.width + implicitHeight: parent.height + radius: parent.radius + opacity: parent.opacity + color: tileColor + } + onClicked: { + if (selectedBeer === 1 || selectedBeer < 0) { + selectedBeer = 0 + displayCount = beer1Count + displayABV = abv + displayFat = 0 + displayCalories = 0 + displayCholesterol = 0 + displaySodium = 0 + displayCarbs = 0 + displayProtein = 0 + sizeSelected = 0 + //6a98c9 + } + } + } + + RectangularGlow { + id: beer2Glow + anchors.fill: beer2 + glowRadius: tileGlowRadius + spread: 0.1 + color: "white" + cornerRadius: beer2.radius + glowRadius + visible: selectedBeer === 1 || selectedBeer === -1 + } + + RoundButton { + id: beer2 + radius: 25 + width: 180 + height: 260 + anchors.verticalCenterOffset: -100 + anchors.horizontalCenterOffset: 125 + anchors.centerIn: parent + // opacity: selectedBeer === 1 ? 1 : 0.5 + property int calories: 132 + property real abv: 5.2 + property real fat: 0 + property real cholesterol: 0 + property real sodium: 0 + property real carbs: 26 + property real protein: 2.5 + + Image { + id: beer2Image + // anchors.fill:parent + source: "Images/Tucher-helles.png" + // sourceSize.width: parent.width + // sourceSize.height: 100 + anchors.centerIn: parent + anchors.verticalCenterOffset: 30 + scale: 0.3 + } + //make beer2Image glow + Glow { + anchors.fill: beer2Image + source: beer2Image + radius: 25 + spread: 0.2 + color: "white" + scale: beer2Image.scale + } + + Text { + text: "Tucher Helles\n Hefe Weizen" + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + font.pointSize: 16 + color: "#FFFFFF" + } + + background: Rectangle { + implicitWidth: parent.width + implicitHeight: parent.height + radius: parent.radius + opacity: parent.opacity + color: tileColor + } + onClicked: { + if (selectedBeer === 0 || selectedBeer < 0) { + selectedBeer = 1 + displayCount = beer2Count + displayABV = abv + displayFat = 0 + displayCalories = 0 + displayCholesterol = 0 + displaySodium = 0 + displayCarbs = 0 + displayProtein = 0 + sizeSelected = 0 + //6a98c9 + } + } + } + + RectangularGlow { + id: caloriesInfoGlow + anchors.fill: caloriesInfo + glowRadius: tileGlowRadius + spread: 0.1 + color: glowColor + cornerRadius: caloriesInfo.radius + glowRadius + } + + Behavior on glowColor { + ColorAnimation { + duration: 2000 + } + } + + Timer { + id: colorTimer + interval: 5000 + repeat: true + running: true + property int colorIndex: 0 + // Define a list of colors representing the RGB spectrum + property var colorList: ["#ff0000", // Red + "#ff7f00", // Orange + "#ffff00", // Yellow + "#00ff00", // Green + "#00ffff", // Cyan + "#0000ff", // Blue + "#8a2be2", // Blue Violet + "#4b0082", // Indigo + "#9400d3", // Violet + "#ff1493", // Deep Pink + "#ff69b4", // Hot Pink + "#ffc0cb", // Pink + "#ff4500", // Orange Red + "#ff8c00", // Dark Orange + "#ffd700", // Gold + "#adff2f", // Green Yellow + "#32cd32", // Lime Green + "#20b2aa", // Light Sea Green + "#00fa9a", // Medium Spring Green + "#00ced1" // Dark Turquoise + ] + onTriggered: { + glowColor = colorList[colorIndex] + colorIndex = (colorIndex + 1) % colorList.length + } + } + + Rectangle { + id: caloriesInfo + x: 40 + anchors.top: beer1.top + width: 150 + height: 150 + color: tileColor + radius: 25 + anchors.verticalCenterOffset: -25 + + //border.color: glowcolor + Text { + id: caloriesNum + text: displayCalories + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -20 + anchors.horizontalCenter: parent.horizontalCenter + font.pointSize: 48 + color: "#FFFFFF" + } + + Text { + text: "Calories" + anchors.top: caloriesNum.bottom + anchors.horizontalCenter: parent.horizontalCenter + font.pointSize: 16 + color: "#FFFFFF" + } + } + + RectangularGlow { + id: abvInfoGlow + anchors.fill: abvInfo + glowRadius: tileGlowRadius + spread: 0.1 + color: glowColor + cornerRadius: abvInfo.radius + glowRadius + } + + Rectangle { + id: abvInfo + //ABV % + x: 822 + y: 240 + width: 150 + height: 150 + color: tileColor + radius: 25 + Text { + id: abvNum + text: Math.round(displayABV * 100) / 100 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -20 + anchors.horizontalCenter: parent.horizontalCenter + font.pointSize: 48 + color: "#FFFFFF" + } + + Text { + text: "% ABV" + anchors.top: abvNum.bottom + anchors.horizontalCenter: parent.horizontalCenter + font.pointSize: 16 + color: "#FFFFFF" + } + } + + RectangularGlow { + id: pouredInfoGlow + anchors.fill: pouredInfo + glowRadius: tileGlowRadius + spread: 0.1 + color: glowColor + cornerRadius: pouredInfo.radius + glowRadius + } + + Rectangle { + id: pouredInfo + x: 822 + anchors.top: beer2.top + width: 150 + height: 150 + color: tileColor + //border.color: glowcolor + radius: 25 + anchors.verticalCenterOffset: -25 + Text { + id: pouredNum + text: displayCount + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -20 + anchors.horizontalCenter: parent.horizontalCenter + font.pointSize: 48 + color: "#FFFFFF" + } + + Text { + text: "Total Ordered" + anchors.top: pouredNum.bottom + anchors.horizontalCenter: parent.horizontalCenter + font.pointSize: 16 + color: "#FFFFFF" + } + } + + RectangularGlow { + id: shortOptionButtonGlow + anchors.fill: shortOptionButton + glowRadius: tileGlowRadius + spread: 0.1 + color: "white" + cornerRadius: shortOptionButton.radius + glowRadius + visible: sizeSelected === 1 || sizeSelected === -1 + } + + RoundButton { + id: shortOptionButton + // x: 304 + y: 314 + anchors.verticalCenter: abvInfo.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + anchors.horizontalCenterOffset: -75 + anchors.verticalCenterOffset: 30 + width: 180 / 2 + height: 65 / 2 + enabled: selectedBeer === 0 || selectedBeer === 1 + Text { + text: "0,33L" + anchors.centerIn: parent + font.pointSize: 16 + color: "#FFFFFF" + } + + background: Rectangle { + implicitWidth: parent.width + implicitHeight: parent.height + radius: parent.radius + opacity: parent.opacity + color: tileColor + } + onClicked: { + sizeSelected = 1 + if (selectedBeer === 0) { + displayCalories = beer1.calories * (1 - .34) + displayFat = beer1.fat * (1 - .34) + displayCholesterol = beer1.cholesterol * (1 - .34) + displaySodium = beer1.sodium * (1 - .34) + displayCarbs = beer1.carbs * (1 - .34) + displayProtein = beer1.protein * (1 - .34) + } else if (selectedBeer === 1) { + displayCalories = beer2.calories * (1 - .34) + displayFat = beer2.fat * (1 - .34) + displayCholesterol = beer2.cholesterol * (1 - .34) + displaySodium = beer2.sodium * (1 - .34) + displayCarbs = beer2.carbs * (1 - .34) + displayProtein = beer2.protein * (1 - .34) + } + } + } + + RectangularGlow { + id: tallOptionButtonGlow + anchors.fill: tallOptionButton + glowRadius: tileGlowRadius + spread: 0.1 + color: "white" + cornerRadius: tallOptionButton.radius + glowRadius + visible: sizeSelected === 2 || sizeSelected === -1 + } + + RoundButton { + id: tallOptionButton + // x: 516 + y: 314 + width: 180 / 2 + height: 65 / 2 + enabled: (selectedBeer >= 0) + anchors.verticalCenter: abvInfo.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + anchors.horizontalCenterOffset: 75 + anchors.verticalCenterOffset: 30 + radius: 25 + + Text { + text: "0,5L" + font.pointSize: 16 + anchors.centerIn: parent + color: "#FFFFFF" + } + + background: Rectangle { + implicitWidth: parent.width + implicitHeight: parent.height + radius: parent.radius + opacity: parent.opacity + color: tileColor + } + onClicked: { + sizeSelected = 2 + if (selectedBeer === 0) { + displayCalories = beer1.calories + displayABV = beer1.abv + displayFat = beer1.fat + displayCholesterol = beer1.cholesterol + displaySodium = beer1.sodium + displayCarbs = beer1.carbs + displayProtein = beer1.protein + } else if (selectedBeer === 1) { + displayCalories = beer2.calories + displayABV = beer2.abv + displayFat = beer2.fat + displayCholesterol = beer2.cholesterol + displaySodium = beer2.sodium + displayCarbs = beer2.carbs + displayProtein = beer2.protein + } + } + } + + RectangularGlow { + id: pourButtonGlow + anchors.fill: pourButton + glowRadius: tileGlowRadius + spread: 0.1 + color: "white" + cornerRadius: pourButton.radius + glowRadius + visible: pourButton.enabled + } + + RoundButton { + id: pourButton + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: nutritionInfo.bottom + width: 180 + height: 65 + // radius: 25 + enabled: sizeSelected > 0 + opacity: enabled ? 1 : 0.75 + Text { + text: "Give me beer!" + font.pointSize: 16 + anchors.centerIn: parent + // color: "#FFFFFF" + } + + onClicked: { + if (selectedBeer) { + beer2Count += 1 + } else { + beer1Count += 1 + } + + selectedBeer = -1 + sizeSelected = 0 + displayCount = 0 + displayABV = 0 + displayCalories = 0 + displayCholesterol = 0 + displayProtein = 0 + displayFat = 0 + displaySodium = 0 + displayCarbs = 0 + + MyTap.run_pump_for_seconds(0.5) + } + } + + Timer { + property bool on: true + running: selectedBeer < 0 + repeat: true + interval: 750 + onTriggered: { + if (on) { + selectedBeer = -1 + } else { + selectedBeer = -2 + } + on = !on + } + } + + Timer { + property bool on: true + running: sizeSelected <= 0 && selectedBeer >= 0 + repeat: true + interval: 750 + onTriggered: { + if (on) { + sizeSelected = -1 + } else { + sizeSelected = 0 + } + on = !on + } + } +} diff --git a/at_Qt_Brew/BrewMonitor/BottomBar.qml b/at_Qt_Brew/BrewMonitor/BottomBar.qml new file mode 100644 index 0000000..fa905ac --- /dev/null +++ b/at_Qt_Brew/BrewMonitor/BottomBar.qml @@ -0,0 +1,61 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 + +Item { + width: Constants.width + height: 50 + Rectangle { + //background color + color: "#002125" + anchors.fill: parent + } + + Image { + id: atsignLogo + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.leftMargin: 10 + anchors.bottomMargin: 10 + sourceSize.height: 50 + source: "Images/atsign-logo-light.svg" + } + + Button { + id: homeButton + anchors.horizontalCenter: parent.horizontalCenter + anchors.horizontalCenterOffset: -85 + anchors.verticalCenter: parent.verticalCenter + text: "Home" + font.pointSize: 16 + width: 150 + onClicked: { + if (!(stackView.currentItem instanceof Beer)) { + stackView.replace("Beer.qml", StackView.PopTransition) + } + } + } + + Button { + text: "SSH No Ports" + font.pointSize: 16 + anchors.left: homeButton.right + anchors.verticalCenter: homeButton.verticalCenter + anchors.leftMargin: 20 + width: 150 + onClicked: { + if (!(stackView.currentItem instanceof About)) { + stackView.replace("About.qml", StackView.PushTransition) + } + } + } + + Image { + id: qtLogo + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.rightMargin: 10 + anchors.bottomMargin: 10 + sourceSize.height: 50 + source: "Images/Qt-logo-white.svg" + } +} diff --git a/at_Qt_Brew/BrewMonitor/Constants.qml b/at_Qt_Brew/BrewMonitor/Constants.qml new file mode 100644 index 0000000..98a8530 --- /dev/null +++ b/at_Qt_Brew/BrewMonitor/Constants.qml @@ -0,0 +1,48 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +pragma Singleton + +import QtQuick + +QtObject { + readonly property int width: 1024 + readonly property int height: 600 + readonly property int tickRate: 5000 + property int timeStep: 0 + property string currentView: "Home" + + property bool isBigDesktopLayout: false + property bool isSmallDesktopLayout: true + property bool isMobileLayout: false + property bool isSmallLayout: false + + property font smallTitleFont: Qt.font({ + "family": "Inter", + "pixelSize": 14, + "weight": 700 + }) + + property font mobileTitleFont: Qt.font({ + "family": "Titillium Web", + "pixelSize": 24, + "weight": 600 + }) + + property font desktopTitleFont: Qt.font({ + "family": "Titillium Web", + "pixelSize": 48, + "weight": 600 + }) + + property font desktopSubtitleFont: Qt.font({ + "family": "Titillium Web", + "pixelSize": 24, + "weight": 600 + }) + + property color backgroundColor: AppSettings.isDarkTheme ? "#000000" : "#EFFCF6" + property color accentColor: "#002125" + property color primaryTextColor: "#FFFFFF" + property color accentTextColor: AppSettings.isDarkTheme ? "#D9D9D9" : "#898989" + property color iconColor: AppSettings.isDarkTheme ? "#D9D9D9" : "#00414A" +} diff --git a/at_Qt_Brew/BrewMonitor/Home.qml b/at_Qt_Brew/BrewMonitor/Home.qml new file mode 100644 index 0000000..e8f2fef --- /dev/null +++ b/at_Qt_Brew/BrewMonitor/Home.qml @@ -0,0 +1,262 @@ +// Copyright (C) 2023 The Qt Company Ltd. +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Effects + +Page { + id: root + + header: ToolBar { + id: toolBar + + background: Rectangle { + color: Constants.accentColor + } + + RowLayout { + anchors.fill: parent + Image { + id: qtLogo + Layout.topMargin: 6 + Layout.bottomMargin: 6 + Layout.leftMargin: 38 + source: AppSettings.isDarkTheme ? "Images/Qt-logo-white" : "Images/Qt-logo-black" + sourceSize.height: 50 + } + + Image { + id: atsignLogo + Layout.topMargin: 6 + Layout.bottomMargin: 6 + Layout.leftMargin: 38 + source: AppSettings.isDarkTheme ? "Images/atsign-logo-light" : "Images/atsign-logo-dark" + sourceSize.height: 50 + } + + Item { + Layout.fillWidth: true + } + + Image { + id: themeTapButton + + source: "Images/theme.svg" + sourceSize.height: Constants.isSmallLayout ? 15 : 20 + sourceSize.width: Constants.isSmallLayout ? 15 : 20 + Layout.rightMargin: Constants.isSmallLayout ? 5 : 19 + visible: Constants.isSmallLayout || Constants.isMobileLayout + + TapHandler { + onTapped: AppSettings.isDarkTheme = !AppSettings.isDarkTheme + } + } + } + } + + background: Rectangle { + color: Constants.accentColor + } + + StackView { + id: stackView + anchors { + left: sideMenu.right + right: parent.right + top: parent.top + bottom: parent.bottom + } + + initialItem: Stats {} + + + } + + SideBar { + id: sideMenu + anchors { + left: parent.left + top: parent.top + bottom: parent.bottom + topMargin: 63 + } + height: parent.height + menuOptions: menuItems + } + + BottomBar { + id: bottomMenu + + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + width: parent.width + + visible: false + position: TabBar.Footer + menuOptions: menuItems + } + + ListModel { + id: menuItems + + ListElement { + name: qsTr("Home") + view: "Stats" + iconSource: "home.svg" + } + ListElement { + name: qsTr("Graphs") + view: "Graphs" + iconSource: "stats.svg" + } + ListElement { + name: qsTr("Water") + view: "Water" + iconSource: "watering-can.svg" + } + ListElement { + name: qsTr("Settings") + view: "Settings" + iconSource: "settings.svg" + } + ListElement { + name: qsTr("About") + view: "About" + iconSource: "question.svg" + } + } + + states: [ + State { + name: "mobileLayout" + when: Constants.isMobileLayout + + PropertyChanges { + target: sideMenu + visible: false + } + PropertyChanges { + target: bottomMenu + visible: true + } + PropertyChanges { + target: stackView + anchors.leftMargin: 0 + } + PropertyChanges { + target: qtLogo + Layout.leftMargin: 19 + sourceSize.height: 25 + Layout.topMargin: 7 + Layout.bottomMargin: 7 + } + PropertyChanges { + target: atsignLogo + Layout.leftMargin: 19 + sourceSize.height: 25 + Layout.topMargin: 7 + Layout.bottomMargin: 7 + } + PropertyChanges { + target: toolBar + height: 39 + } + AnchorChanges { + target: stackView + anchors.left: parent.left + anchors.bottom: bottomMenu.top + } + // StateChangeScript { + // name: "enteringMobileLayout" + // script: console.log("entering mobileLayout state") + // } + }, + State { + name: "desktopLayout" + when: Constants.isBigDesktopLayout || Constants.isSmallDesktopLayout + + PropertyChanges { + target: sideMenu + visible: true + anchors.topMargin: 63 + } + PropertyChanges { + target: bottomMenu + visible: false + } + PropertyChanges { + target: stackView + anchors.leftMargin: 5 + } + PropertyChanges { + target: qtLogo + Layout.leftMargin: 38 + sourceSize.height: 50 + Layout.topMargin: 6 + Layout.bottomMargin: 6 + } + PropertyChanges { + target: atsignLogo + Layout.leftMargin: 38 + sourceSize.height: 50 + Layout.topMargin: 6 + Layout.bottomMargin: 6 + } + PropertyChanges { + target: toolBar + height: 56 + } + AnchorChanges { + target: stackView + anchors.left: sideMenu.right + anchors.bottom: parent.bottom + } + }, + State { + name: "smallLayout" + when: Constants.isSmallLayout + + PropertyChanges { + target: sideMenu + visible: true + anchors.topMargin: 24 + } + PropertyChanges { + target: bottomMenu + visible: false + } + PropertyChanges { + target: stackView + anchors.leftMargin: 5 + } + PropertyChanges { + target: qtLogo + Layout.leftMargin: 5 + sourceSize.height: 18 + Layout.topMargin: 5 + Layout.bottomMargin: 0 + } + PropertyChanges { + target: atsignLogo + Layout.leftMargin: 5 + sourceSize.height: 18 + Layout.topMargin: 5 + Layout.bottomMargin: 0 + } + PropertyChanges { + target: toolBar + height: 24 + } + AnchorChanges { + target: stackView + anchors.left: sideMenu.right + anchors.bottom: parent.bottom + } + // StateChangeScript { + // name: "enteringSmallLayout" + // script: console.log("entering smallLayout state") + // } + } + ] +} diff --git a/at_Qt_Brew/BrewMonitor/Images/Budweiser.jpg b/at_Qt_Brew/BrewMonitor/Images/Budweiser.jpg new file mode 100644 index 0000000..e9e9a3a Binary files /dev/null and b/at_Qt_Brew/BrewMonitor/Images/Budweiser.jpg differ diff --git a/at_Qt_Brew/BrewMonitor/Images/Budweiser.png b/at_Qt_Brew/BrewMonitor/Images/Budweiser.png new file mode 100644 index 0000000..8d33068 Binary files /dev/null and b/at_Qt_Brew/BrewMonitor/Images/Budweiser.png differ diff --git a/at_Qt_Brew/BrewMonitor/Images/Budweiser.svg b/at_Qt_Brew/BrewMonitor/Images/Budweiser.svg new file mode 100644 index 0000000..103d313 Binary files /dev/null and b/at_Qt_Brew/BrewMonitor/Images/Budweiser.svg differ diff --git a/at_Qt_Brew/BrewMonitor/Images/CES-slides.pdf b/at_Qt_Brew/BrewMonitor/Images/CES-slides.pdf new file mode 100644 index 0000000..0a13581 Binary files /dev/null and b/at_Qt_Brew/BrewMonitor/Images/CES-slides.pdf differ diff --git a/at_Qt_Brew/BrewMonitor/Images/Qt-Group-logo-black.png b/at_Qt_Brew/BrewMonitor/Images/Qt-Group-logo-black.png new file mode 100644 index 0000000..51f985e Binary files /dev/null and b/at_Qt_Brew/BrewMonitor/Images/Qt-Group-logo-black.png differ diff --git a/at_Qt_Brew/BrewMonitor/Images/Qt-Group-logo-white.png b/at_Qt_Brew/BrewMonitor/Images/Qt-Group-logo-white.png new file mode 100644 index 0000000..cb014ff Binary files /dev/null and b/at_Qt_Brew/BrewMonitor/Images/Qt-Group-logo-white.png differ diff --git a/at_Qt_Brew/BrewMonitor/Images/Qt-logo-black.svg b/at_Qt_Brew/BrewMonitor/Images/Qt-logo-black.svg new file mode 100644 index 0000000..62604ff --- /dev/null +++ b/at_Qt_Brew/BrewMonitor/Images/Qt-logo-black.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/at_Qt_Brew/BrewMonitor/Images/Qt-logo-white.svg b/at_Qt_Brew/BrewMonitor/Images/Qt-logo-white.svg new file mode 100644 index 0000000..5671258 --- /dev/null +++ b/at_Qt_Brew/BrewMonitor/Images/Qt-logo-white.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/at_Qt_Brew/BrewMonitor/Images/Tucher-helles.png b/at_Qt_Brew/BrewMonitor/Images/Tucher-helles.png new file mode 100644 index 0000000..5890aa1 Binary files /dev/null and b/at_Qt_Brew/BrewMonitor/Images/Tucher-helles.png differ diff --git a/at_Qt_Brew/BrewMonitor/Images/Tucher-logo.jpg b/at_Qt_Brew/BrewMonitor/Images/Tucher-logo.jpg new file mode 100644 index 0000000..52a9219 Binary files /dev/null and b/at_Qt_Brew/BrewMonitor/Images/Tucher-logo.jpg differ diff --git a/at_Qt_Brew/BrewMonitor/Images/atsign-logo-dark.svg b/at_Qt_Brew/BrewMonitor/Images/atsign-logo-dark.svg new file mode 100644 index 0000000..ce56059 --- /dev/null +++ b/at_Qt_Brew/BrewMonitor/Images/atsign-logo-dark.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/at_Qt_Brew/BrewMonitor/Images/atsign-logo-light.svg b/at_Qt_Brew/BrewMonitor/Images/atsign-logo-light.svg new file mode 100644 index 0000000..59a1b74 --- /dev/null +++ b/at_Qt_Brew/BrewMonitor/Images/atsign-logo-light.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/at_Qt_Brew/BrewMonitor/Images/tucher.jpg b/at_Qt_Brew/BrewMonitor/Images/tucher.jpg new file mode 100644 index 0000000..6c4b834 Binary files /dev/null and b/at_Qt_Brew/BrewMonitor/Images/tucher.jpg differ diff --git a/at_Qt_Brew/BrewMonitor/Main.qml b/at_Qt_Brew/BrewMonitor/Main.qml new file mode 100644 index 0000000..1db3029 --- /dev/null +++ b/at_Qt_Brew/BrewMonitor/Main.qml @@ -0,0 +1,33 @@ +import QtQuick +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Pdf + +Window { + width: Constants.width + height: Constants.height - 50 + visible: true + title: qsTr("Beer!") + + // color: "#002125" + StackView { + id: stackView + width: Constants.width + height: Constants.height - 50 + initialItem: Beer { + id: beer + anchors.fill: parent + } + + About { + id: about + anchors.fill: parent + } + } + + BottomBar { + anchors.bottom: parent.bottom + width: parent.width + } +} diff --git a/at_Qt_Brew/BrewMonitor/TODO b/at_Qt_Brew/BrewMonitor/TODO new file mode 100644 index 0000000..e69de29 diff --git a/at_Qt_Brew/BrewMonitor/qmldir b/at_Qt_Brew/BrewMonitor/qmldir new file mode 100644 index 0000000..d8d70fe --- /dev/null +++ b/at_Qt_Brew/BrewMonitor/qmldir @@ -0,0 +1,7 @@ +module BrewMonitor +singleton AppSettings 1.0 AppSettings.qml +singleton Constants 1.0 Constants.qml +Beer 1.0 Beer.qml +Home 1.0 Home.qml +Main 1.0 Main.qml +BottomBar 1.0 BottomBar.qml diff --git a/at_Qt_Brew/atBrewDemo.pyproject b/at_Qt_Brew/atBrewDemo.pyproject new file mode 100644 index 0000000..7a96777 --- /dev/null +++ b/at_Qt_Brew/atBrewDemo.pyproject @@ -0,0 +1,22 @@ +{ + "files": [ + "main.py", + "BrewMonitor/AppSettings.qml", + "BrewMonitor/Constants.qml", + "BrewMonitor/About.qml", + "BrewMonitor/BottomBar.qml", + "BrewMonitor/Beer.qml", + "BrewMonitor/Main.qml", + "tucher.jpg", + "Budweiser.svg", + "Images/Qt-logo-black.svg", + "Images/Qt-logo-white.svg", + "Images/atsign-logo-dark.svg", + "Images/atsign-logo-light.svg", + "Images/CES-slides.pdf", + "Images/Tucher-helles.png", + "Images/Budweiser.png", + "BrewMonitor/TODO", + "beerTap_controller.py" + ] +} diff --git a/at_Qt_Brew/atsign/README.md b/at_Qt_Brew/atsign/README.md new file mode 100644 index 0000000..43da658 --- /dev/null +++ b/at_Qt_Brew/atsign/README.md @@ -0,0 +1,7 @@ +# atsign + +The Atsign portion of this demo uses the [Python SDK](https://github.com/atsign-foundation/at_python) + +``` +pip install atsdk +``` \ No newline at end of file diff --git a/at_Qt_Brew/atsign/app/app.py b/at_Qt_Brew/atsign/app/app.py new file mode 100644 index 0000000..e30282c --- /dev/null +++ b/at_Qt_Brew/atsign/app/app.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +from at_client.connections.notification.atevents import AtEvent, AtEventType +from at_client.util.encryptionutil import EncryptionUtil +from at_client.common.keys import AtKey, Metadata, SharedKey +from at_client.common import AtSign +from at_client import AtClient +import threading +import json +import queue +from time import sleep, time + + +# sends the notification + +# @plant:pump.qtplant@qtapp +# { +# “timestamp”: 3129319391 +# “type”: “pumpWithSeconds” +# “data”: { +# “seconds”: 3 +# } +# } + +beer_atsign = '@qt_beer' +app_atsign = '@qt_app_2' +namespace = 'qtbeer' + +# Sends a notification to the beer atsign to run the pump for `seconds` seconds +# @param atclient: authenticated AtClient object, +# @param seconds: int, number of seconds to run the pump +# @return: notification id that was sent and the timestamp of the notification + + +def send_run_pump_with_seconds(atclient: AtClient, seconds: int): + timestamp = int(time()) + data = {'seconds': seconds} + payload = { + 'timestamp': timestamp, + 'type': 'pumpWithSeconds', + 'data': data + } + + data_to_send = json.dumps(payload) + + sharedkey = SharedKey.from_string( + str(beer_atsign) + ':pump.qtbeer' + str(app_atsign)) + metadata = Metadata( + ttl=3*1000, # ttl = 3 second in miliseconds + ttr=-1, # do not refresh, we don't care about any subsequent changes to this atkey + iv_nonce=EncryptionUtil.generate_iv_nonce(), # required for encryption + ) + sharedkey.metadata = metadata + + sleep(1) + + res = atclient.notify(sharedkey, data_to_send) + return (res, timestamp) + + +def main(): + atclient = AtClient(AtSign(app_atsign), queue=queue.Queue( + maxsize=100), verbose=False) + thread = threading.Thread(target=atclient.start_monitor, args=(namespace,)) + thread.start() + + (notification_id, timestamp) = send_run_pump_with_seconds(atclient, 0.5) + print('Sent Notification ID: %s' % notification_id) + + # go through queue + found_ack = False + while not found_ack: + at_event = atclient.queue.get() + event_type = at_event.event_type + if event_type == AtEventType.UPDATE_NOTIFICATION: + # push it back onto the queue + atclient.handle_event(atclient.queue, at_event) + continue + if event_type != AtEventType.DECRYPTED_UPDATE_NOTIFICATION: + continue + + event_data = at_event.event_data + print('Event Data: %s' % (str(event_data))) + + # Listen for acks + decrypted_value = json.loads(event_data['decryptedValue']) + data = decrypted_value['data'] + + if decrypted_value['type'] == 'ack' and data['timestamp'] == timestamp: + # Received acknowledgement + print('Received Acknowledgement') + found_ack = True + cmd = "notify:remove:" + str(event_data["id"]) + print('Executing \'%s\'' % (cmd)) + atclient.secondary_connection.execute_command(cmd, retry_on_exception=3) + + thread.join() + atclient.stop_monitor() + + +if __name__ == '__main__': + main() diff --git a/at_Qt_Brew/atsign/beer/beer.py b/at_Qt_Brew/atsign/beer/beer.py new file mode 100644 index 0000000..c7d07d5 --- /dev/null +++ b/at_Qt_Brew/atsign/beer/beer.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +from at_client.connections.notification.atevents import AtEvent, AtEventType +from at_client.util.encryptionutil import EncryptionUtil +from at_client.common.keys import AtKey, Metadata, SharedKey +from at_client.common import AtSign +from at_client import AtClient +import threading +import json +import queue +from time import sleep, time +import RPi.GPIO as GPIO + +DC_WATER_PUMP = 4 # GPIO PIN = 4 +GPIO.setmode(GPIO.BCM) +GPIO.setup(DC_WATER_PUMP, GPIO.OUT) + +beer_atsign = '@qt_beer' +app_atsign = '@qt_app_2' +namespace = 'qtbeer' + +# Sends a notification to the app atsign that we have acknowledged the event. timestamp is not the current timestamp. timestamp is the timestamp of the notification that we are acknowledging (the timestamp came from the app and was generated by the app). +# @param atclient: authenticated AtClient object, +# @param timestamp: int, timestamp of the notification that we are acknowledging +# @return: notification id that was sent +def send_ack(atclient: AtClient, timestamp: int): + data = {'timestamp': timestamp} + payload = { + 'type': 'ack', + 'timestamp': int(time()), + 'data': data + } + + data_to_send = json.dumps(payload) + + sharedkey = SharedKey.from_string(str(app_atsign) + ':ack.qtbeer' + str(beer_atsign)) + metadata = Metadata( + ttl = 3*1000, # ttl = 3 second in miliseconds + ttr = -1, # do not refresh, we don't care about any subsequent changes to this atkey + iv_nonce = EncryptionUtil.generate_iv_nonce() , # required for encryption + ) + sharedkey.metadata = metadata + + sleep(1) + + print('Sent %s %s' %(sharedkey, data_to_send)) + + res = atclient.notify(sharedkey, data_to_send) + return res + +def run_pump(seconds: float): + GPIO.output(DC_WATER_PUMP, GPIO.HIGH) + sleep(seconds) + GPIO.output(DC_WATER_PUMP, GPIO.LOW) + +def main(): + print('Authenticating...') + atclient = AtClient(AtSign(beer_atsign), queue=queue.Queue(maxsize=100), verbose=True) + if atclient == None: + print('Failed to authenticate.') + return + threading.Thread(target=atclient.start_monitor, args=(namespace,)).start() + + + while True: + at_event = atclient.queue.get() + event_type = at_event.event_type + event_data = at_event.event_data + if event_type == AtEventType.UPDATE_NOTIFICATION: + atclient.handle_event(atclient.queue, at_event) + cmd = "notify:remove:" + str(event_data["id"]) + # print('Executing \'%s\'' %(cmd)) + atclient.secondary_connection.execute_command(cmd, retry_on_exception=3) + pass + if event_type != AtEventType.DECRYPTED_UPDATE_NOTIFICATION: + continue + + # Now we are ready to process decrypted event data + print('Event Data:') + print(json.dumps(event_data, indent=4)) + + decrypted_value = json.loads(event_data['decryptedValue']) + + timestamp = decrypted_value['timestamp'] + type = decrypted_value['type'] + data = decrypted_value['data'] + + notification_id = send_ack(atclient, timestamp) + print('Sent ack: %s' %(notification_id)) + + if type == 'pumpWithSeconds': + seconds = data['seconds'] + print('Pumping for %f seconds' % seconds) + run_pump(float(seconds)) + sleep(1) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/at_Qt_Brew/atsign/beer/beer.sh b/at_Qt_Brew/atsign/beer/beer.sh new file mode 100644 index 0000000..69102da --- /dev/null +++ b/at_Qt_Brew/atsign/beer/beer.sh @@ -0,0 +1,4 @@ +#!/bin/bash +while true; do + python3 beer.py +done diff --git a/at_Qt_Brew/beerTap_controller.py b/at_Qt_Brew/beerTap_controller.py new file mode 100644 index 0000000..98e076d --- /dev/null +++ b/at_Qt_Brew/beerTap_controller.py @@ -0,0 +1,52 @@ +from PySide6.QtQml import QmlElement, QmlSingleton +from PySide6.QtCore import QObject, Slot, Property, Signal, Qt +from at_client.connections.notification.atevents import AtEvent, AtEventType +from at_client.util.encryptionutil import EncryptionUtil +from at_client.common import AtSign +from at_client.common.keys import AtKey, Metadata, SharedKey +from at_client import AtClient +from time import sleep, time +import threading +import queue +import datetime +import json +import random + + +QML_IMPORT_NAME = "io.qt.brew" +QML_IMPORT_MAJOR_VERSION = 1 + +#@QmlElement +@QmlSingleton +class BeerTap(QObject): + def __init__(self): + QObject.__init__(self) + self.local = AtSign("@qt_app_2") + self.local_atclient = AtClient(self.local, queue=queue.Queue(maxsize=100), verbose=False) + + self.remote = AtSign("@qt_beer") + + @Slot(float) + def run_pump_for_seconds(self, seconds: float = 0.5): + atclient = self.local_atclient + qt_app_atsign = self.local + beer_atsign = self.remote + + timestamp = int(time()) + data = {'seconds': seconds} + payload = { + 'timestamp': timestamp, + 'type': 'pumpWithSeconds', + 'data': data + } + payload = json.dumps(payload) + sharedkey = SharedKey.from_string(str(beer_atsign) + ':pump.qtbeer' + str(qt_app_atsign)) + iv_nonce = EncryptionUtil.generate_iv_nonce() + metadata = Metadata( + ttl=3000, + ttr=-1, + iv_nonce=iv_nonce, + ) + sharedkey.metadata = metadata + sleep(1) + res = atclient.notify(sharedkey, payload) diff --git a/at_Qt_Brew/main.py b/at_Qt_Brew/main.py new file mode 100644 index 0000000..4d27072 --- /dev/null +++ b/at_Qt_Brew/main.py @@ -0,0 +1,22 @@ +# This Python file uses the following encoding: utf-8 +import sys +from pathlib import Path +import beerTap_controller + +from PySide6.QtWidgets import QApplication +from PySide6.QtQml import QQmlApplicationEngine, qmlRegisterSingletonType + + +if __name__ == "__main__": + app = QApplication(sys.argv) + QApplication.setApplicationName("BrewMonitorApp"); + QApplication.setOrganizationName("QtProject"); + + engine = QQmlApplicationEngine() + qmlRegisterSingletonType(beerTap_controller.BeerTap, "BeerTap", 1, 0, "MyTap") + + engine.addImportPath(Path(__file__).parent) + engine.loadFromModule("BrewMonitor", "Main") + if not engine.rootObjects(): + sys.exit(-1) + sys.exit(app.exec())