summaryrefslogtreecommitdiff
path: root/package/contents
diff options
context:
space:
mode:
Diffstat (limited to 'package/contents')
-rw-r--r--package/contents/config/config.qml11
-rw-r--r--package/contents/config/main.xml32
-rw-r--r--package/contents/ui/CompactRepresentation.qml33
-rw-r--r--package/contents/ui/ConfigGeneral.qml80
-rw-r--r--package/contents/ui/FormatUtils.qml31
-rw-r--r--package/contents/ui/FullRepresentation.qml203
-rw-r--r--package/contents/ui/IconUtils.qml31
-rw-r--r--package/contents/ui/main.qml161
8 files changed, 582 insertions, 0 deletions
diff --git a/package/contents/config/config.qml b/package/contents/config/config.qml
new file mode 100644
index 0000000..1b48b5d
--- /dev/null
+++ b/package/contents/config/config.qml
@@ -0,0 +1,11 @@
+import QtQuick 6.0
+
+import org.kde.plasma.configuration 2.0 as PlasmaConfig
+
+PlasmaConfig.ConfigModel {
+ PlasmaConfig.ConfigCategory {
+ name: i18n("General")
+ icon: "configure"
+ source: "ConfigGeneral.qml"
+ }
+}
diff --git a/package/contents/config/main.xml b/package/contents/config/main.xml
new file mode 100644
index 0000000..d33f3dd
--- /dev/null
+++ b/package/contents/config/main.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 http://www.kde.org/standards/kcfg/1.0/kcfg.xsd">
+ <kcfgfile name=""/>
+
+ <group name="General">
+ <entry name="mqttClientId" type="String">
+ <label>MQTT client ID</label>
+ <default>camper-widget-refresh</default>
+ </entry>
+
+ <entry name="mqttHosts" type="String">
+ <label>MQTT hosts (comma separated list)</label>
+ <default>127.0.0.1:1883</default>
+ </entry>
+
+ <entry name="mqttUsername" type="String">
+ <label>MQTT username</label>
+ <default></default>
+ </entry>
+
+ <entry name="mqttPassword" type="String">
+ <label>MQTT password</label>
+ <default></default>
+ </entry>
+
+ <entry name="refreshIntervalSeconds" type="Int">
+ <label>Refresh interval (seconds)</label>
+ <default>60</default>
+ <min>1</min>
+ </entry>
+ </group>
+</kcfg>
diff --git a/package/contents/ui/CompactRepresentation.qml b/package/contents/ui/CompactRepresentation.qml
new file mode 100644
index 0000000..85d62ee
--- /dev/null
+++ b/package/contents/ui/CompactRepresentation.qml
@@ -0,0 +1,33 @@
+import QtQuick 6.0
+import QtQuick.Layouts 6.0
+import org.kde.plasma.components 3.0 as PlasmaComponents
+
+RowLayout {
+ IconUtils { id: icons }
+ FormatUtils { id: fmt }
+ spacing: 4
+
+ PlasmaComponents.Label {
+ text: icons.getDirectionIcon(root.direction)
+ font.pointSize: 12
+ }
+
+ PlasmaComponents.Label {
+ text: icons.getBatteryIcon(root.batterySoc)
+ font.pointSize: 12
+ }
+
+ PlasmaComponents.Label {
+ text: fmt.formatSoc(root.batterySoc)
+ font.pointSize: 10
+ font.bold: true
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.LeftButton
+ onClicked: (mouse) => {
+ root.expanded = !root.expanded
+ }
+ }
+}
diff --git a/package/contents/ui/ConfigGeneral.qml b/package/contents/ui/ConfigGeneral.qml
new file mode 100644
index 0000000..9428ed0
--- /dev/null
+++ b/package/contents/ui/ConfigGeneral.qml
@@ -0,0 +1,80 @@
+import QtQuick 6.0
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 6.0 as Controls
+import org.kde.kirigami as Kirigami
+import org.kde.kcmutils as KCM
+import org.kde.plasma.workspace.dbus as DBus
+
+KCM.SimpleKCM {
+ id: page
+
+ // KCM properties that will be automatically managed
+ property alias cfg_mqttClientId: mqttClientIdField.text
+ property alias cfg_mqttHosts: mqttHostsField.text
+ property alias cfg_mqttUsername: mqttUsernameField.text
+ property alias cfg_mqttPassword: mqttPasswordField.text
+ property alias cfg_refreshIntervalSeconds: refreshIntervalField.value
+
+ // Default values for KCM system
+ property string cfg_mqttClientIdDefault: "camper-widget-refresh"
+ property string cfg_mqttHostsDefault: "127.0.0.1:1883"
+ property string cfg_mqttUsernameDefault: ""
+ property string cfg_mqttPasswordDefault: ""
+ property int cfg_refreshIntervalSecondsDefault: 60
+
+ Kirigami.FormLayout {
+ anchors.fill: parent
+
+ Controls.TextField {
+ id: mqttClientIdField
+ Kirigami.FormData.label: i18n("MQTT client ID")
+ placeholderText: "camper-widget-refresh"
+ }
+
+ Controls.TextField {
+ id: mqttHostsField
+ Kirigami.FormData.label: i18n("MQTT hosts")
+ placeholderText: "127.0.0.1:1883"
+ Controls.ToolTip.visible: hovered
+ Controls.ToolTip.text: i18n("Comma separated list, e.g. 127.0.0.1 or 127.0.0.1,192.168.1.111 or 127.0.0.1:1883,192.168.1.111:1883")
+ }
+
+ Controls.TextField {
+ id: mqttUsernameField
+ Kirigami.FormData.label: i18n("MQTT username")
+ placeholderText: ""
+ }
+
+ Controls.TextField {
+ id: mqttPasswordField
+ Kirigami.FormData.label: i18n("MQTT password")
+ echoMode: Controls.TextField.Password
+ placeholderText: ""
+ }
+
+ Controls.SpinBox {
+ id: refreshIntervalField
+ Kirigami.FormData.label: i18n("Refresh interval (seconds)")
+ from: 1
+ to: 86400
+ stepSize: 1
+ }
+ }
+
+ // Notify config changes when KCM properties change
+ onCfg_mqttClientIdChanged: notifyConfigChanged()
+ onCfg_mqttHostsChanged: notifyConfigChanged()
+ onCfg_mqttUsernameChanged: notifyConfigChanged()
+ onCfg_mqttPasswordChanged: notifyConfigChanged()
+ onCfg_refreshIntervalSecondsChanged: notifyConfigChanged()
+
+ function notifyConfigChanged() {
+ DBus.SessionBus.asyncCall({
+ service: "org.craftknight.CamperWidget",
+ path: "/org/craftknight/camper_widget",
+ iface: "org.craftknight.CamperWidget.Config",
+ member: "Reload",
+ signature: ""
+ })
+ }
+}
diff --git a/package/contents/ui/FormatUtils.qml b/package/contents/ui/FormatUtils.qml
new file mode 100644
index 0000000..9fe400a
--- /dev/null
+++ b/package/contents/ui/FormatUtils.qml
@@ -0,0 +1,31 @@
+import QtQuick 6.0
+
+QtObject {
+ function formatSoc(soc) {
+ if (soc === undefined || soc === null || isNaN(soc) || soc < 0) {
+ return "--"
+ }
+ return Math.round(soc) + "%"
+ }
+
+ function formatPower(connected, power) {
+ if (!connected) {
+ return "--"
+ }
+ return Math.round(power) + "W"
+ }
+
+ function formatSolar(solarPower) {
+ if (solarPower === undefined || solarPower === null || isNaN(solarPower) || solarPower < 0) {
+ return "--"
+ }
+ return Math.round(solarPower) + "W"
+ }
+
+ function formatAcPower(power) {
+ if (power === undefined || power === null || isNaN(power) || power < 0) {
+ return "--"
+ }
+ return Math.round(power) + "W"
+ }
+}
diff --git a/package/contents/ui/FullRepresentation.qml b/package/contents/ui/FullRepresentation.qml
new file mode 100644
index 0000000..b76da53
--- /dev/null
+++ b/package/contents/ui/FullRepresentation.qml
@@ -0,0 +1,203 @@
+import QtQuick 6.0
+import QtQuick.Layouts 6.0
+import org.kde.kirigami as Kirigami
+import org.kde.plasma.components 3.0 as PlasmaComponents
+
+ColumnLayout {
+ IconUtils { id: icons }
+ FormatUtils { id: fmt }
+ anchors.fill: parent
+ spacing: 8
+
+ // Header
+ PlasmaComponents.Label {
+ Layout.alignment: Qt.AlignCenter
+ text: "Camper Van Energy System"
+ font.pointSize: 14
+ font.bold: true
+ }
+
+ Kirigami.Separator {
+ Layout.fillWidth: true
+ }
+
+ // Status availability indicator
+ PlasmaComponents.Label {
+ Layout.alignment: Qt.AlignCenter
+ text: root.connected ? "Status: Connected" : "Status: Disconnected"
+ color: root.connected ? "green" : "red"
+ font.pointSize: 10
+ }
+
+ // Battery level
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 8
+
+ PlasmaComponents.Label {
+ text: icons.getBatteryIcon(root.batterySoc)
+ font.pointSize: 16
+ }
+
+ PlasmaComponents.Label {
+ text: "Battery Level:"
+ font.pointSize: 11
+ }
+
+ PlasmaComponents.Label {
+ Layout.fillWidth: true
+ text: fmt.formatSoc(root.batterySoc)
+ font.pointSize: 11
+ font.bold: true
+ horizontalAlignment: Text.AlignRight
+ }
+ }
+
+ // System power
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 8
+
+ PlasmaComponents.Label {
+ text: "\uf0e7" //
+ font.pointSize: 16
+ }
+
+ PlasmaComponents.Label {
+ text: "System Power:"
+ font.pointSize: 11
+ }
+
+ PlasmaComponents.Label {
+ Layout.fillWidth: true
+ text: fmt.formatPower(root.connected, root.batteryPower)
+ font.pointSize: 11
+ font.bold: true
+ horizontalAlignment: Text.AlignRight
+ }
+ }
+
+ // Solar power
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 8
+
+ PlasmaComponents.Label {
+ text: "\uf185" //
+ font.pointSize: 16
+ }
+
+ PlasmaComponents.Label {
+ text: "Solar Power:"
+ font.pointSize: 11
+ }
+
+ PlasmaComponents.Label {
+ Layout.fillWidth: true
+ text: fmt.formatSolar(root.solarPower)
+ font.pointSize: 11
+ font.bold: true
+ horizontalAlignment: Text.AlignRight
+ }
+ }
+
+ // AC input
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 8
+
+ PlasmaComponents.Label {
+ text: "\uf1e6" //
+ font.pointSize: 16
+ }
+
+ PlasmaComponents.Label {
+ text: "AC Input:"
+ font.pointSize: 11
+ }
+
+ PlasmaComponents.Label {
+ Layout.fillWidth: true
+ text: fmt.formatAcPower(root.acInputPower)
+ font.pointSize: 11
+ font.bold: true
+ horizontalAlignment: Text.AlignRight
+ }
+ }
+
+ // AC load
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 8
+
+ PlasmaComponents.Label {
+ text: "\uf2db" //
+ font.pointSize: 16
+ }
+
+ PlasmaComponents.Label {
+ text: "AC Load:"
+ font.pointSize: 11
+ }
+
+ PlasmaComponents.Label {
+ Layout.fillWidth: true
+ text: fmt.formatAcPower(root.acLoadPower)
+ font.pointSize: 11
+ font.bold: true
+ horizontalAlignment: Text.AlignRight
+ }
+ }
+
+ // Status direction
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 8
+
+ PlasmaComponents.Label {
+ text: "\uf1fe" //
+ font.pointSize: 16
+ }
+
+ PlasmaComponents.Label {
+ text: "Status:"
+ font.pointSize: 11
+ }
+
+ PlasmaComponents.Label {
+ Layout.fillWidth: true
+ text: root.direction
+ font.pointSize: 11
+ font.bold: true
+ horizontalAlignment: Text.AlignRight
+ }
+ }
+
+ // Last updated
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 8
+
+ PlasmaComponents.Label {
+ text: "\uf017" //
+ font.pointSize: 16
+ }
+
+ PlasmaComponents.Label {
+ text: "Last Updated:"
+ font.pointSize: 11
+ }
+
+ PlasmaComponents.Label {
+ Layout.fillWidth: true
+ text: root.lastUpdated
+ font.pointSize: 11
+ font.bold: true
+ horizontalAlignment: Text.AlignRight
+ }
+ }
+
+ Kirigami.Separator {
+ Layout.fillWidth: true
+ }
+}
diff --git a/package/contents/ui/IconUtils.qml b/package/contents/ui/IconUtils.qml
new file mode 100644
index 0000000..344d26f
--- /dev/null
+++ b/package/contents/ui/IconUtils.qml
@@ -0,0 +1,31 @@
+import QtQuick 6.0
+
+QtObject {
+ function getBatteryIcon(soc) {
+ if (soc === undefined || soc === null || soc < 0) {
+ return "\uf244"; // 
+ }
+ var socNum = Math.floor(soc);
+ if (socNum <= 20) return "\uf244"; // 
+ if (socNum <= 40) return "\uf243"; // 
+ if (socNum <= 60) return "\uf242"; // 
+ if (socNum <= 80) return "\uf241"; // 
+ return "\uf240"; // 
+ }
+
+ function getDirectionIcon(direction) {
+ if (!direction) return "";
+ switch (direction) {
+ case "charge":
+ return "\uf185"; // 
+ case "discharge":
+ return "\uf0e7"; // 
+ case "idle":
+ return "\uf186"; // 
+ default:
+ return "";
+ }
+ }
+}
+
+
diff --git a/package/contents/ui/main.qml b/package/contents/ui/main.qml
new file mode 100644
index 0000000..26b5045
--- /dev/null
+++ b/package/contents/ui/main.qml
@@ -0,0 +1,161 @@
+import QtQuick 6.0
+import org.kde.plasma.plasmoid 2.0
+import org.kde.plasma.workspace.dbus as DBus
+
+PlasmoidItem {
+ id: root
+
+ // Exposed properties for child components
+ property bool connected: false
+ property real batterySoc: -1
+ property real batteryPower: 0
+ property real solarPower: -1
+ property real acInputPower: -1
+ property real acLoadPower: -1
+ property string direction: "idle"
+ property string lastUpdated: "unknown"
+
+ // Watch for service availability
+ DBus.DBusServiceWatcher {
+ id: serviceWatcher
+ watchedService: "org.craftknight.CamperWidget"
+ busType: DBus.BusType.Session
+
+ onRegisteredChanged: {
+ if (registered) {
+ statusProps.updateAll()
+ batteryProps.updateAll()
+ solarProps.updateAll()
+ acInputProps.updateAll()
+ acLoadProps.updateAll()
+ } else {
+ root.connected = false
+ root.batterySoc = -1
+ root.batteryPower = 0
+ root.solarPower = -1
+ root.acInputPower = -1
+ root.acLoadPower = -1
+ root.direction = "idle"
+ root.lastUpdated = "unknown"
+ }
+ }
+ }
+
+ // Status interface - Connected property
+ DBus.Properties {
+ id: statusProps
+ service: "org.craftknight.CamperWidget"
+ path: "/org/craftknight/camper_widget"
+ iface: "org.craftknight.CamperWidget.Status"
+ busType: DBus.BusType.Session
+
+ onPropertiesChanged: function(iface, changed, invalidated) {
+ if ("Connected" in changed)
+ root.connected = Boolean(changed["Connected"])
+ updateTimestamp()
+ }
+ onRefreshed: {
+ var c = properties.Connected
+ if (c !== undefined) root.connected = Boolean(c)
+ }
+ }
+
+ // Battery interface - Soc, Power, State
+ DBus.Properties {
+ id: batteryProps
+ service: "org.craftknight.CamperWidget"
+ path: "/org/craftknight/camper_widget/Battery"
+ iface: "org.craftknight.CamperWidget.Battery"
+ busType: DBus.BusType.Session
+
+ onPropertiesChanged: function(iface, changed, invalidated) {
+ if ("Soc" in changed)
+ root.batterySoc = Number(changed["Soc"])
+ if ("Power" in changed)
+ root.batteryPower = Number(changed["Power"])
+ if ("State" in changed)
+ root.direction = mapDirection(String(changed["State"]))
+ updateTimestamp()
+ }
+ onRefreshed: {
+ var s = properties.Soc
+ var p = properties.Power
+ var st = properties.State
+ if (s !== undefined) root.batterySoc = Number(s)
+ if (p !== undefined) root.batteryPower = Number(p)
+ if (st !== undefined) root.direction = mapDirection(String(st))
+ }
+ }
+
+ // Solar interface - Power
+ DBus.Properties {
+ id: solarProps
+ service: "org.craftknight.CamperWidget"
+ path: "/org/craftknight/camper_widget/Solar"
+ iface: "org.craftknight.CamperWidget.Solar"
+ busType: DBus.BusType.Session
+
+ onPropertiesChanged: function(iface, changed, invalidated) {
+ if ("Power" in changed)
+ root.solarPower = Number(changed["Power"])
+ updateTimestamp()
+ }
+ onRefreshed: {
+ var p = properties.Power
+ if (p !== undefined) root.solarPower = Number(p)
+ }
+ }
+
+ // AC input interface - Power
+ DBus.Properties {
+ id: acInputProps
+ service: "org.craftknight.CamperWidget"
+ path: "/org/craftknight/camper_widget/AcInput"
+ iface: "org.craftknight.CamperWidget.AcInput"
+ busType: DBus.BusType.Session
+
+ onPropertiesChanged: function(iface, changed, invalidated) {
+ if ("Power" in changed)
+ root.acInputPower = Number(changed["Power"])
+ updateTimestamp()
+ }
+ onRefreshed: {
+ var p = properties.Power
+ if (p !== undefined) root.acInputPower = Number(p)
+ }
+ }
+
+ // AC load interface - Power
+ DBus.Properties {
+ id: acLoadProps
+ service: "org.craftknight.CamperWidget"
+ path: "/org/craftknight/camper_widget/AcLoad"
+ iface: "org.craftknight.CamperWidget.AcLoad"
+ busType: DBus.BusType.Session
+
+ onPropertiesChanged: function(iface, changed, invalidated) {
+ if ("Power" in changed)
+ root.acLoadPower = Number(changed["Power"])
+ updateTimestamp()
+ }
+ onRefreshed: {
+ var p = properties.Power
+ if (p !== undefined) root.acLoadPower = Number(p)
+ }
+ }
+
+ function mapDirection(state) {
+ switch (state) {
+ case "charging": return "charge"
+ case "discharging": return "discharge"
+ default: return "idle"
+ }
+ }
+
+ function updateTimestamp() {
+ root.lastUpdated = Qt.formatDateTime(new Date(), "hh:mm:ss")
+ }
+
+ compactRepresentation: CompactRepresentation {}
+ fullRepresentation: FullRepresentation {}
+}