import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import org.kde.plasma.plasmoid
import org.kde.plasma.core as PlasmaCore
import org.kde.plasma.components as PlasmaComponents3
import org.kde.kirigami as Kirigami
PlasmoidItem {
id: root
// Panel widget sizing - compact for panel mode
Layout.minimumWidth: hasValidConfig && hasEntities ?
(configuredEntities.length * (Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.smallSpacing)) + Kirigami.Units.smallSpacing :
Kirigami.Units.gridUnit * 3
Layout.minimumHeight: Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.smallSpacing * 2
Layout.preferredWidth: hasValidConfig && hasEntities ?
(configuredEntities.length * (Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.smallSpacing)) + Kirigami.Units.smallSpacing :
Kirigami.Units.gridUnit * 3
Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.smallSpacing * 2
switchWidth: Kirigami.Units.gridUnit * 20
switchHeight: Kirigami.Units.gridUnit * 16
Plasmoid.backgroundHints: PlasmaCore.Types.DefaultBackground | PlasmaCore.Types.ConfigurableBackground
property string homeAssistantUrl: plasmoid.configuration.homeAssistantUrl
property string accessToken: plasmoid.configuration.accessToken
property int updateInterval: plasmoid.configuration.updateInterval
property string configuredEntitiesJson: plasmoid.configuration.configuredEntities
property int maxEntitiesPerRow: plasmoid.configuration.maxEntitiesPerRow
property bool showEntityLabels: plasmoid.configuration.showEntityLabels
property string buttonSize: plasmoid.configuration.buttonSize
property var configuredEntities: []
property var entityStates: ({}) // Store entity states here
property bool hasValidConfig: homeAssistantUrl && accessToken
property bool hasEntities: configuredEntities.length > 0
// For expanded popup with advanced controls
property string expandedEntityId: ""
property var expandedEntityData: null
// Parse configured entities from JSON
function parseConfiguredEntities() {
try {
var parsed = JSON.parse(configuredEntitiesJson || "[]")
configuredEntities = parsed
} catch (e) {
console.log("Error parsing configured entities:", e)
configuredEntities = []
}
}
// Update entity states
function updateEntityStates() {
if (!hasValidConfig || !hasEntities) return
for (var i = 0; i < configuredEntities.length; i++) {
var entity = configuredEntities[i]
homeAssistantAPI.getState(entity.entityId)
}
}
onConfiguredEntitiesJsonChanged: parseConfiguredEntities()
Component.onCompleted: parseConfiguredEntities()
// Home Assistant API helper
HomeAssistantAPI {
id: homeAssistantAPI
baseUrl: root.homeAssistantUrl
accessToken: root.accessToken
onStateChanged: function(entityId, state) {
// Store the state in our central state store
var newStates = root.entityStates
newStates[entityId] = state
root.entityStates = newStates
// Trigger a change notification
root.entityStatesChanged()
}
onError: function(message) {
console.log("Home Assistant API Error:", message)
}
}
// Update timer
Timer {
id: updateTimer
interval: root.updateInterval * 1000
running: root.hasValidConfig && root.hasEntities
repeat: true
onTriggered: updateEntityStates()
}
fullRepresentation: ColumnLayout {
anchors.fill: parent
anchors.margins: Kirigami.Units.smallSpacing
// Advanced lighting controls popup for the selected entity
Rectangle {
visible: expandedEntityId !== ""
Layout.fillWidth: true
Layout.fillHeight: true
color: Kirigami.Theme.backgroundColor
border.color: Kirigami.Theme.separatorColor
border.width: 1
radius: Kirigami.Units.cornerRadius
EntityControl {
anchors.fill: parent
anchors.margins: Kirigami.Units.largeSpacing * 2
entityId: root.expandedEntityId
displayName: root.expandedEntityData ? (root.expandedEntityData.displayName || root.expandedEntityData.entityId) : ""
controlType: root.expandedEntityData ? (root.expandedEntityData.controlType || "toggle") : "toggle"
iconName: root.expandedEntityData ? (root.expandedEntityData.icon || "home-assistant") : "home-assistant"
showLabel: true
buttonSize: "large"
entityState: root.entityStates[root.expandedEntityId] || null
showExpandedControls: true
onControlActivated: function(entityId, action, data) {
switch (action) {
case "toggle":
homeAssistantAPI.toggleEntity(entityId)
break
case "turn_on":
homeAssistantAPI.turnOn(entityId, data)
break
case "turn_off":
homeAssistantAPI.turnOff(entityId)
break
}
// Update state after a short delay
Qt.callLater(function() {
homeAssistantAPI.getState(entityId)
})
}
onStateRequested: function(entityId) {
if (root.hasValidConfig) {
homeAssistantAPI.getState(entityId)
}
}
}
// Close button
PlasmaComponents3.Button {
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: Kirigami.Units.smallSpacing
icon.name: "window-close"
flat: true
onClicked: {
root.expandedEntityId = ""
root.expandedEntityData = null
}
}
}
// Regular grid view when no specific entity is expanded
Item {
visible: expandedEntityId === ""
Layout.fillWidth: true
Layout.fillHeight: true
// Title
PlasmaComponents3.Label {
id: titleLabel
text: i18n("Home Assistant Control")
font.bold: true
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
visible: !hasValidConfig || !hasEntities
}
// Configuration prompt
ColumnLayout {
visible: !hasValidConfig
anchors.fill: parent
anchors.topMargin: titleLabel.height + Kirigami.Units.largeSpacing
spacing: Kirigami.Units.largeSpacing
PlasmaComponents3.Label {
text: i18n("Configure the widget to connect to your Home Assistant instance")
wrapMode: Text.WordWrap
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
}
PlasmaComponents3.Button {
text: i18n("Configure Connection")
Layout.alignment: Qt.AlignHCenter
onClicked: plasmoid.internalAction("configure").trigger()
}
}
// Entity setup prompt
ColumnLayout {
visible: hasValidConfig && !hasEntities
anchors.fill: parent
anchors.topMargin: titleLabel.height + Kirigami.Units.largeSpacing
spacing: Kirigami.Units.largeSpacing
PlasmaComponents3.Label {
text: i18n("Configure entities to control them from the widget")
wrapMode: Text.WordWrap
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
}
PlasmaComponents3.Button {
text: i18n("Configure Entities")
Layout.alignment: Qt.AlignHCenter
onClicked: plasmoid.internalAction("configure").trigger()
}
}
// Entity controls grid
QQC2.ScrollView {
id: scrollView
visible: hasValidConfig && hasEntities
anchors.fill: parent
GridLayout {
id: entityGrid
columns: root.maxEntitiesPerRow
columnSpacing: Kirigami.Units.smallSpacing
rowSpacing: Kirigami.Units.smallSpacing
Repeater {
model: root.configuredEntities
EntityControl {
entityId: modelData.entityId || ""
displayName: modelData.displayName || modelData.entityId || ""
controlType: modelData.controlType || "toggle"
iconName: modelData.icon || "home-assistant"
showLabel: root.showEntityLabels
buttonSize: root.buttonSize
entityState: root.entityStates[entityId] || null
onControlActivated: function(entityId, action, data) {
switch (action) {
case "toggle":
homeAssistantAPI.toggleEntity(entityId)
break
case "turn_on":
homeAssistantAPI.turnOn(entityId, data)
break
case "turn_off":
homeAssistantAPI.turnOff(entityId)
break
}
// Update state after a short delay
Qt.callLater(function() {
homeAssistantAPI.getState(entityId)
})
}
onStateRequested: function(entityId) {
if (root.hasValidConfig) {
homeAssistantAPI.getState(entityId)
}
}
Component.onCompleted: {
if (root.hasValidConfig) {
homeAssistantAPI.getState(entityId)
}
}
}
}
}
}
}
}
// Helper functions for panel buttons
function getEntityIconName(entityId, iconName, entityState, isOn) {
if (iconName && iconName !== "home-assistant") {
return iconName
}
// Auto-detect icon based on entity type and state
var domain = entityId.split('.')[0]
switch (domain) {
case 'light':
return isOn ? "lightbulb-on" : "lightbulb"
case 'switch':
return isOn ? "toggle-switch" : "toggle-switch-off"
case 'fan':
return isOn ? "weather-windy" : "fan"
case 'automation':
return isOn ? "playlist-play" : "playlist-remove"
case 'climate':
return "thermometer"
case 'lock':
return entityState && entityState.state === "locked" ? "lock" : "lock-open"
case 'cover':
return entityState && entityState.state === "open" ? "window-open" : "window-close"
case 'input_boolean':
return isOn ? "checkbox-marked" : "checkbox-blank-outline"
default:
return "home-assistant"
}
}
function getEntityTooltipText(displayName, entityState, isLight, controlType, hasAdvancedControls) {
var tooltip = displayName
if (!entityState) {
tooltip += "\nState: Unavailable"
return tooltip
}
var stateText = entityState.state || "unknown"
// Use friendly name if available
if (entityState.attributes && entityState.attributes.friendly_name) {
tooltip = entityState.attributes.friendly_name
}
tooltip += "\nState: " + stateText
// Only show basic info in tooltip, since full details are in expanded view
if (entityState.attributes) {
// Show only the most relevant single piece of info
if (entityState.attributes.brightness && (entityState.state === "on")) {
tooltip += "\nBrightness: " + Math.round(entityState.attributes.brightness / 255 * 100) + "%"
} else if (entityState.attributes.temperature !== undefined) {
tooltip += "\nTemperature: " + entityState.attributes.temperature + "°"
} else if (entityState.attributes.battery_level !== undefined) {
tooltip += "\nBattery: " + entityState.attributes.battery_level + "%"
}
}
// Add instruction for expanded view
if (hasAdvancedControls) {
tooltip += "\n\nRight-click for advanced controls & details"
} else {
tooltip += "\n\nRight-click for detailed information"
}
return tooltip
}
function handlePanelEntityClick(entityId, controlType, isOn) {
switch (controlType) {
case "toggle":
homeAssistantAPI.toggleEntity(entityId)
break
case "switch":
homeAssistantAPI.callService(isOn ? "turn_off" : "turn_on", entityId)
break
case "light":
homeAssistantAPI.toggleEntity(entityId)
break
case "status":
// Status displays don't perform actions, just refresh state
homeAssistantAPI.getState(entityId)
return
default:
homeAssistantAPI.toggleEntity(entityId)
break
}
// Always try to refresh state after any action
Qt.callLater(function() {
homeAssistantAPI.getState(entityId)
})
}
compactRepresentation: Item {
anchors.fill: parent
// Configuration prompt when not configured
Rectangle {
visible: !hasValidConfig || !hasEntities
anchors.fill: parent
color: "transparent"
border.color: Kirigami.Theme.separatorColor
border.width: 1
radius: Kirigami.Units.cornerRadius
RowLayout {
anchors.centerIn: parent
anchors.margins: Kirigami.Units.smallSpacing
Kirigami.Icon {
source: "home-assistant"
Layout.preferredWidth: Kirigami.Units.iconSizes.small
Layout.preferredHeight: Kirigami.Units.iconSizes.small
color: Kirigami.Theme.disabledTextColor
}
PlasmaComponents3.Label {
text: i18n("Setup")
font.pointSize: Kirigami.Theme.smallFont.pointSize
color: Kirigami.Theme.disabledTextColor
}
}
PlasmaCore.ToolTipArea {
anchors.fill: parent
mainText: i18n("Home Assistant Control")
subText: !hasValidConfig ? i18n("Click to configure connection") : i18n("Click to configure entities")
}
MouseArea {
anchors.fill: parent
onClicked: plasmoid.internalAction("configure").trigger()
}
}
// Panel entity controls when configured
RowLayout {
visible: hasValidConfig && hasEntities
anchors.fill: parent
anchors.margins: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
Repeater {
model: root.configuredEntities
PlasmaComponents3.Button {
id: panelButton
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
property string entityId: modelData.entityId || ""
property string displayName: modelData.displayName || modelData.entityId || ""
property string controlType: modelData.controlType || "toggle"
property string iconName: modelData.icon || "home-assistant"
property var entityState: root.entityStates[entityId] || null
property bool isOn: entityState ? (entityState.state === "on" || entityState.state === "home" || entityState.state === "open") : false
property bool isLight: entityId.split('.')[0] === 'light'
property bool hasAdvancedControls: isLight && controlType === "light" &&
entityState && entityState.attributes && entityState.attributes.supported_color_modes &&
(entityState.attributes.supported_color_modes.indexOf('rgb') !== -1 ||
entityState.attributes.supported_color_modes.indexOf('color_temp') !== -1 ||
entityState.attributes.supported_color_modes.indexOf('brightness') !== -1)
property var currentRgbColor: entityState && entityState.attributes && entityState.attributes.rgb_color ?
entityState.attributes.rgb_color : [255, 255, 255]
// Visual state based on entity state
property color normalColor: Kirigami.Theme.buttonBackgroundColor
property color activeColor: isOn ?
(isLight && entityState && entityState.attributes && entityState.attributes.rgb_color ?
Qt.rgba(currentRgbColor[0]/255, currentRgbColor[1]/255, currentRgbColor[2]/255, 0.3) :
Kirigami.Theme.positiveBackgroundColor) : normalColor
background: Rectangle {
radius: Kirigami.Units.cornerRadius
color: panelButton.pressed ? Qt.darker(panelButton.activeColor, 1.1) :
panelButton.hovered ? Qt.lighter(panelButton.activeColor, 1.1) :
panelButton.activeColor
border.color: isOn ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.buttonFocusColor
border.width: isOn ? 2 : 1
Behavior on color { ColorAnimation { duration: 150 } }
Behavior on border.color { ColorAnimation { duration: 150 } }
// Advanced controls indicator for lights
Rectangle {
visible: hasAdvancedControls
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: 1
width: 4
height: 4
radius: 2
color: Kirigami.Theme.disabledTextColor
}
}
contentItem: Kirigami.Icon {
source: getEntityIconName(entityId, iconName, entityState, isOn)
color: isOn ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.textColor
Behavior on color { ColorAnimation { duration: 150 } }
}
PlasmaComponents3.ToolTip {
text: getEntityTooltipText(displayName, entityState, isLight, controlType, hasAdvancedControls)
}
onClicked: {
handlePanelEntityClick(entityId, controlType, isOn)
}
// Right-click to show expanded view with entity details
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: {
// Store the current entity for the popup
root.expandedEntityId = entityId
root.expandedEntityData = modelData
root.expanded = true
}
}
Component.onCompleted: {
if (root.hasValidConfig && entityId) {
homeAssistantAPI.getState(entityId)
}
}
}
}
}
}
}
neiam
/home-assistant-plasmoid
Home Assistant Plasmoid
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
22.2 KiB · text
History
0e50366