wew lads

05507c6 · gmorell · 2025-06-23 21:11

14 files +2138 -69

Files changed

modified README.md
+202 −53
@@ -2,72 +2,221 @@
2 2
3 3 This is a Plasma 6 compatible widget for controlling Home Assistant entities from your KDE desktop.
4 4
5 −## Version 2.0 Changes (Plasma 6 Compatibility)
6 −
7 −This widget has been updated for Plasma 6 compatibility with the following changes:
8 −
9 −### Key Updates:
10 −1. **Qt/QML Imports**: Updated to use version-less imports compatible with Qt6
11 −2. **Kirigami Integration**: Added proper Kirigami units and theming
12 −3. **Configuration System**: Updated to use KCM.SimpleKCM for better integration
13 −4. **Icon Handling**: Replaced deprecated PlasmaCore.IconItem with Kirigami.Icon
14 −5. **Internationalization**: Added proper i18n() calls for all user-visible strings
15 −6. **Layout Improvements**: Better responsive design using Kirigami.Units
16 −7. **Tooltips**: Added proper tooltip integration
17 −
18 −### Technical Changes:
19 −- Removed version numbers from import statements
20 −- Updated `PlasmaCore.IconItem` to `Kirigami.Icon`
21 −- Updated configuration UI to use `KCM.SimpleKCM`
22 −- Added `QtQuick.Controls` as `QQC2` alias
23 −- Updated action calls to use `plasmoid.internalAction()`
24 −- Added proper switchWidth/switchHeight properties for compact representation
25 −
26 −## Installation
27 −
28 −The widget is installed in the standard Plasma widget location:
29 −```
30 −~/.local/share/plasma/plasmoids/org.neiam.kde.homeassistant/
31 −```
5 +## Version 2.0 Features
6 +
7 +### ✨ **NEW: Entity Control System**
8 +- **Configure Custom Entities**: Add any Home Assistant entity (lights, switches, automations, etc.)
9 +- **Interactive Controls**: Click to toggle entities directly from your desktop
10 +- **Visual State Feedback**: Buttons show current entity state with color coding
11 +- **Flexible Layout**: Configurable grid layout with 1-6 entities per row
12 +- **Smart Icons**: Auto-detects appropriate icons based on entity type
13 +- **Real-time Updates**: Automatically refreshes entity states at configurable intervals
14 +
15 +### 🔍 **NEW: Entity Autocomplete & Browser**
16 +- **Live Entity Browser**: Browse all entities directly from Home Assistant
17 +- **Search & Filter**: Search by name or entity ID, filter by domain type
18 +- **Visual Entity List**: See entity states, icons, and domain badges
19 +- **Auto-fill Fields**: Automatically populate display names and icons
20 +- **Smart Suggestions**: Recommends appropriate control types per entity
21 +- **Connection Test**: Verify Home Assistant connection before browsing
22 +- **Live Preview**: See exactly how your entity control will look before adding it
23 +- **Interactive Icon Picker**: Browse and select icons visually from Plasma's icon collection
24 +
25 +### 🎛️ **Control Types Supported**
26 +- **Toggle Button**: For lights, switches, automations
27 +- **Switch Control**: Explicit on/off controls
28 +- **Light Control**: Optimized for light entities
29 +- **Status Display**: Read-only state display
30 +
31 +### 🎨 **Customization Options**
32 +- **Button Sizes**: Small, Medium, Large
33 +- **Show/Hide Labels**: Toggle entity names
34 +- **Custom Icons**: Override default icons per entity
35 +- **Grid Layout**: 1-6 entities per row
36 +- **Update Intervals**: 5-300 seconds
32 37
33 38 ## Configuration
34 39
35 −1. Right-click the widget and select "Configure"
36 −2. Enter your Home Assistant URL (e.g., http://homeassistant.local:8123)
37 −3. Enter your long-lived access token from Home Assistant
38 −4. Set the update interval (5-300 seconds)
39 −
40 −## Compatibility
40 +### **Step 1: General Settings**
41 +1. Right-click the widget → "Configure"
42 +2. In the **General** tab:
43 + - Enter your Home Assistant URL (e.g., `http://homeassistant.local:8123`)
44 + - Enter your long-lived access token
45 + - Set update interval (5-300 seconds)
46 +
47 +### **Step 2: Entity Configuration**
48 +1. Switch to the **Entities** tab
49 +2. Configure display options:
50 + - **Entities per row**: 1-6 (default: 3)
51 + - **Show entity labels**: On/Off
52 + - **Button size**: Small/Medium/Large
53 +
54 +3. **Browse & Add Entities** (NEW!):
55 + - **Entity Browser**: Click the dropdown to browse all Home Assistant entities
56 + - **Search**: Type to filter entities by name or ID
57 + - **Domain Filter**: Click buttons to filter by entity type (light, switch, etc.)
58 + - **Visual Selection**: See entity states, icons, and current values
59 + - **Auto-fill**: Selected entities automatically populate fields
60 + - **OR Manual Entry**: Enter entity details manually if needed
61 +
62 +4. **Configure Entity & Preview**:
63 + - **Display Name**: Friendly name (auto-filled from Home Assistant)
64 + - **Control Type**: Auto-suggested based on entity type
65 + - **Icon Selection**:
66 + - Manual entry in text field OR
67 + - **Browse Icons**: Visual icon picker with search and categories
68 + - **Icon Preview**: See current icon in real-time
69 + - **Reset**: Clear custom icon to use auto-detected
70 + - **Live Preview**: See exactly how the control will look with current settings
71 + - **Interactive Preview**: Click the preview to test control behavior (safe mode)
72 + - **Settings Indicators**: Visual indicators for labels, size, and control type
73 + - Click **Add Entity**
74 +
75 +5. **Manage Entities**:
76 + - Swipe left on any entity to remove it
77 + - View entity details and domain badges
78 + - Duplicate entities are automatically updated
79 +
80 +### **Step 3: Usage**
81 +- **Compact Mode**: Shows badge with entity count
82 +- **Click to Expand**: Opens full control panel
83 +- **Entity Controls**: Click buttons to toggle entities
84 +- **Tooltips**: Hover for detailed state information
85 +
86 +## Supported Entity Types
87 +
88 +| Domain | Auto-Icon | Supported Actions |
89 +|--------|-----------|-------------------|
90 +| `light.*` | 💡 Lightbulb | Toggle, Turn On/Off |
91 +| `switch.*` | 🔌 Toggle Switch | Toggle, Turn On/Off |
92 +| `fan.*` | 🌪️ Windy | Toggle, Turn On/Off |
93 +| `automation.*` | ▶️ Play/Stop | Toggle, Turn On/Off |
94 +| `input_boolean.*` | ☑️ Checkbox | Toggle |
95 +| `climate.*` | 🌡️ Thermometer | Status Display |
96 +| `lock.*` | 🔒 Lock | Status Display |
97 +| `cover.*` | 🪟 Window | Status Display |
98 +
99 +## Entity ID Examples
41 100
42 −- **Plasma 6+**: Fully compatible
43 −- **Qt 6+**: Required
44 −- **KDE Frameworks 6+**: Required
101 +```
102 +# Lights
103 +light.living_room
104 +light.bedroom_lamp
105 +light.kitchen_ceiling
106 +
107 +# Switches
108 +switch.living_room_fan
109 +switch.coffee_maker
110 +switch.outdoor_lights
111 +
112 +# Automations
113 +automation.morning_routine
114 +automation.security_mode
115 +automation.bedtime_lights
116 +
117 +# Input Booleans
118 +input_boolean.guest_mode
119 +input_boolean.vacation_mode
120 +
121 +# Climate
122 +climate.thermostat
123 +climate.bedroom_ac
124 +```
45 125
46 −## Development Notes
126 +## Plasma 6 Compatibility Updates
47 127
48 −This widget serves as a foundation for Home Assistant integration. Future versions may include:
49 −- Entity state display
50 −- Entity control buttons
51 −- Custom entity selection
52 −- Real-time updates via WebSocket
128 +### Key Technical Changes:
129 +- ✅ **Qt6 Compatible**: Version-less imports
130 +- ✅ **Kirigami Integration**: Proper theming and units
131 +- ✅ **KCM Configuration**: Modern config system
132 +- ✅ **Component Updates**: Replaced deprecated elements
133 +- ✅ **API Integration**: Full Home Assistant REST API support
134 +- ✅ **State Management**: Real-time entity state tracking
135 +- ✅ **Error Handling**: Robust error handling and logging
53 136
54 −## Files Structure
137 +### Files Structure
55 138
56 139 ```
57 140 contents/
58 141 ├── config/
59 −│ ├── config.qml # Configuration categories
60 −│ └── main.xml # Configuration schema
142 +│ ├── config.qml # Configuration categories
143 +│ └── main.xml # Configuration schema
61 144 └── ui/
62 − ├── main.qml # Main widget UI
63 − └── configGeneral.qml # Configuration page UI
64 −metadata.json # Widget metadata
145 + ├── main.qml # Main widget UI with entity grid
146 + ├── configGeneral.qml # Connection configuration
147 + ├── configEntities.qml # Entity management UI
148 + ├── HomeAssistantAPI.qml # API communication layer
149 + └── EntityControl.qml # Individual entity control component
150 +metadata.json # Widget metadata
151 +```
152 +
153 +## Home Assistant Setup
154 +
155 +### 1. Create Long-Lived Access Token
156 +1. Open Home Assistant
157 +2. Go to Profile → Security
158 +3. Scroll to "Long-Lived Access Tokens"
159 +4. Click "Create Token"
160 +5. Name it (e.g., "Plasma Widget")
161 +6. Copy the token (you won't see it again!)
162 +
163 +### 2. Test API Access
164 +```bash
165 +# Test connection (replace URL and TOKEN)
166 +curl -X GET \
167 + -H "Authorization: Bearer YOUR_TOKEN_HERE" \
168 + -H "Content-Type: application/json" \
169 + http://homeassistant.local:8123/api/
65 170 ```
66 171
67 172 ## Troubleshooting
68 173
69 −If the widget doesn't load:
70 −1. Check Plasma version (must be 6+)
71 −2. Verify all files are in the correct location
72 −3. Restart Plasma: `systemctl --user restart plasma-plasmashell`
73 −4. Check system logs: `journalctl --user -f | grep plasma`
174 +### Widget Not Loading
175 +1. Check Plasma version: `plasmashell --version` (must be 6+)
176 +2. Verify file permissions: `ls -la ~/.local/share/plasma/plasmoids/`
177 +3. Check logs: `journalctl --user -f | grep plasma`
178 +
179 +### Connection Issues
180 +1. Verify Home Assistant URL is accessible
181 +2. Test API token in browser/curl
182 +3. Check firewall settings
183 +4. Ensure Home Assistant is running
184 +
185 +### Entity Control Issues
186 +1. Verify entity IDs exist in Home Assistant
187 +2. Check entity permissions (some may be read-only)
188 +3. Review Home Assistant logs for API errors
189 +4. Test entity control in Home Assistant UI first
190 +
191 +### Performance Tips
192 +- Use longer update intervals (60+ seconds) for better performance
193 +- Limit entities to 6-12 for optimal responsiveness
194 +- Use "Status Display" for read-only entities
195 +
196 +## Advanced Usage
197 +
198 +### Custom Icons
199 +Use any KDE icon name or file path:
200 +- `lightbulb`, `power-socket`, `fan`
201 +- `weather-clear`, `weather-cloudy`
202 +- `/path/to/custom/icon.svg`
203 +
204 +### Entity Naming
205 +- Use descriptive names: "Living Room Lights" vs "light.living_room"
206 +- Keep names short for better layout
207 +- Use consistent naming conventions
208 +
209 +## Compatibility
210 +
211 +- **Plasma 6+**: Required
212 +- **Qt 6+**: Required
213 +- **KDE Frameworks 6+**: Required
214 +- **Home Assistant 2023.1+**: Recommended
215 +
216 +## Contributing
217 +
218 +This widget is open source. Feel free to contribute improvements:
219 +- Enhanced entity types support
220 +- Additional control types
221 +- UI/UX improvements
222 +- Performance optimizations
modified contents/config/config.qml
+5 −0
@@ -7,4 +7,9 @@ ConfigModel {
7 7 icon: "configure"
8 8 source: "configGeneral.qml"
9 9 }
10 + ConfigCategory {
11 + name: i18n("Entities")
12 + icon: "view-list-details"
13 + source: "configEntities.qml"
14 + }
10 15 }
modified contents/config/main.xml
+18 −3
@@ -23,9 +23,24 @@
23 23 </group>
24 24
25 25 <group name="Entities">
26 − <entry name="selectedEntities" type="StringList">
27 − <label>Selected Entities</label>
28 − <default></default>
26 + <entry name="configuredEntities" type="String">
27 + <label>Configured Entities (JSON)</label>
28 + <default>[]</default>
29 + </entry>
30 +
31 + <entry name="maxEntitiesPerRow" type="Int">
32 + <label>Maximum entities per row</label>
33 + <default>3</default>
34 + </entry>
35 +
36 + <entry name="showEntityLabels" type="Bool">
37 + <label>Show entity labels</label>
38 + <default>true</default>
39 + </entry>
40 +
41 + <entry name="buttonSize" type="String">
42 + <label>Button size</label>
43 + <default>medium</default>
29 44 </entry>
30 45 </group>
31 46 </kcfg>
added contents/ui/EntityBrowser.qml
+300 −0
@@ -0,0 +1,300 @@
1 +import QtQuick
2 +import QtQuick.Controls as QQC2
3 +import QtQuick.Layouts
4 +import org.kde.kirigami as Kirigami
5 +
6 +QQC2.ComboBox {
7 + id: entityBrowser
8 +
9 + property alias homeAssistantAPI: homeAssistantAPI
10 + property string selectedEntityId: ""
11 + property string selectedEntityName: ""
12 + property var selectedEntity: null
13 + property var filteredDomains: [] // Empty = all domains, or ["light", "switch"] etc.
14 + property string searchQuery: ""
15 +
16 + signal entitySelected(var entity)
17 +
18 + editable: true
19 + textRole: "display_text"
20 + valueRole: "entity_id"
21 +
22 + // Custom model for entities
23 + model: ListModel {
24 + id: entityModel
25 + }
26 +
27 + onEditTextChanged: {
28 + searchQuery = editText
29 + filterEntities()
30 + }
31 +
32 + onActivated: function(index) {
33 + if (index >= 0 && index < entityModel.count) {
34 + var entity = entityModel.get(index)
35 + selectedEntityId = entity.entity_id
36 + selectedEntityName = entity.friendly_name
37 + selectedEntity = entity
38 + entitySelected(entity)
39 + }
40 + }
41 +
42 + // Custom popup for better entity browsing
43 + popup: QQC2.Popup {
44 + id: entityPopup
45 + y: entityBrowser.height
46 + width: Math.max(entityBrowser.width, Kirigami.Units.gridUnit * 20)
47 + height: Math.min(contentItem.implicitHeight, Kirigami.Units.gridUnit * 15)
48 +
49 + contentItem: ColumnLayout {
50 + spacing: Kirigami.Units.smallSpacing
51 +
52 + // Domain filter
53 + RowLayout {
54 + Layout.fillWidth: true
55 +
56 + QQC2.Label {
57 + text: i18n("Filter:")
58 + font.pointSize: Kirigami.Theme.smallFont.pointSize
59 + }
60 +
61 + Repeater {
62 + model: ["all", "light", "switch", "fan", "automation", "climate", "cover", "lock"]
63 +
64 + QQC2.Button {
65 + text: modelData === "all" ? i18n("All") : modelData
66 + checkable: true
67 + checked: filteredDomains.length === 0 && modelData === "all" ||
68 + filteredDomains.indexOf(modelData) !== -1
69 +
70 + onClicked: {
71 + if (modelData === "all") {
72 + filteredDomains = []
73 + } else {
74 + var domains = filteredDomains.slice() // copy array
75 + var index = domains.indexOf(modelData)
76 + if (index !== -1) {
77 + domains.splice(index, 1)
78 + } else {
79 + domains.push(modelData)
80 + }
81 + filteredDomains = domains
82 + }
83 + filterEntities()
84 + }
85 +
86 + font.pointSize: Kirigami.Theme.smallFont.pointSize
87 + Layout.preferredHeight: Kirigami.Units.gridUnit * 1.2
88 + }
89 + }
90 + }
91 +
92 + Kirigami.Separator {
93 + Layout.fillWidth: true
94 + }
95 +
96 + // Entity list
97 + QQC2.ScrollView {
98 + Layout.fillWidth: true
99 + Layout.fillHeight: true
100 +
101 + ListView {
102 + id: entityListView
103 + model: entityModel
104 +
105 + delegate: QQC2.ItemDelegate {
106 + width: entityListView.width
107 +
108 + contentItem: RowLayout {
109 + spacing: Kirigami.Units.smallSpacing
110 +
111 + // Domain badge
112 + Rectangle {
113 + Layout.preferredWidth: Kirigami.Units.gridUnit * 2
114 + Layout.preferredHeight: Kirigami.Units.gridUnit * 0.8
115 + radius: Kirigami.Units.cornerRadius
116 + color: getDomainColor(model.domain)
117 +
118 + QQC2.Label {
119 + anchors.centerIn: parent
120 + text: model.domain
121 + color: "white"
122 + font.pointSize: Kirigami.Theme.smallFont.pointSize
123 + font.bold: true
124 + }
125 + }
126 +
127 + // Entity icon
128 + Kirigami.Icon {
129 + source: getEntityIcon(model)
130 + Layout.preferredWidth: Kirigami.Units.iconSizes.small
131 + Layout.preferredHeight: Kirigami.Units.iconSizes.small
132 + }
133 +
134 + // Entity info
135 + ColumnLayout {
136 + Layout.fillWidth: true
137 + spacing: 0
138 +
139 + QQC2.Label {
140 + text: model.friendly_name
141 + font.bold: true
142 + Layout.fillWidth: true
143 + elide: Text.ElideRight
144 + }
145 +
146 + QQC2.Label {
147 + text: model.entity_id + " • " + model.state
148 + color: Kirigami.Theme.disabledTextColor
149 + font.pointSize: Kirigami.Theme.smallFont.pointSize
150 + Layout.fillWidth: true
151 + elide: Text.ElideRight
152 + }
153 + }
154 +
155 + // State indicator
156 + Rectangle {
157 + Layout.preferredWidth: Kirigami.Units.gridUnit * 0.5
158 + Layout.preferredHeight: Kirigami.Units.gridUnit * 0.5
159 + radius: width / 2
160 + color: getStateColor(model.state)
161 + }
162 + }
163 +
164 + onClicked: {
165 + entityBrowser.currentIndex = index
166 + selectedEntityId = model.entity_id
167 + selectedEntityName = model.friendly_name
168 + selectedEntity = model
169 + entitySelected(model)
170 + entityPopup.close()
171 + }
172 + }
173 + }
174 + }
175 +
176 + // Status bar
177 + RowLayout {
178 + Layout.fillWidth: true
179 +
180 + QQC2.Label {
181 + text: i18n("%1 entities found", entityModel.count)
182 + font.pointSize: Kirigami.Theme.smallFont.pointSize
183 + color: Kirigami.Theme.disabledTextColor
184 + }
185 +
186 + Item { Layout.fillWidth: true }
187 +
188 + QQC2.BusyIndicator {
189 + visible: homeAssistantAPI.isLoadingEntities
190 + Layout.preferredWidth: Kirigami.Units.iconSizes.small
191 + Layout.preferredHeight: Kirigami.Units.iconSizes.small
192 + }
193 + }
194 + }
195 + }
196 +
197 + // Home Assistant API instance
198 + HomeAssistantAPI {
199 + id: homeAssistantAPI
200 +
201 + onEntitiesLoaded: function(entities) {
202 + filterEntities()
203 + }
204 +
205 + onError: function(message) {
206 + console.log("Entity browser API error:", message)
207 + }
208 + }
209 +
210 + function loadEntities() {
211 + if (homeAssistantAPI.baseUrl && homeAssistantAPI.accessToken) {
212 + homeAssistantAPI.getAllEntities()
213 + }
214 + }
215 +
216 + function filterEntities() {
217 + entityModel.clear()
218 +
219 + if (!homeAssistantAPI.allEntities || homeAssistantAPI.allEntities.length === 0) {
220 + return
221 + }
222 +
223 + var searchResults = homeAssistantAPI.searchEntities(searchQuery, filteredDomains)
224 +
225 + // Limit results to prevent UI lag
226 + var maxResults = 100
227 + for (var i = 0; i < Math.min(searchResults.length, maxResults); i++) {
228 + var entity = searchResults[i]
229 + entityModel.append({
230 + entity_id: entity.entity_id,
231 + friendly_name: entity.friendly_name,
232 + domain: entity.domain,
233 + state: entity.state,
234 + icon: entity.icon,
235 + device_class: entity.device_class,
236 + unit_of_measurement: entity.unit_of_measurement,
237 + display_text: entity.friendly_name + " (" + entity.entity_id + ")"
238 + })
239 + }
240 + }
241 +
242 + function getDomainColor(domain) {
243 + switch (domain) {
244 + case "light": return "#FFA726"
245 + case "switch": return "#42A5F5"
246 + case "fan": return "#66BB6A"
247 + case "automation": return "#AB47BC"
248 + case "climate": return "#EF5350"
249 + case "cover": return "#26A69A"
250 + case "lock": return "#8D6E63"
251 + case "sensor": return "#78909C"
252 + case "binary_sensor": return "#90A4AE"
253 + default: return "#9E9E9E"
254 + }
255 + }
256 +
257 + function getEntityIcon(entity) {
258 + if (entity.icon) {
259 + return entity.icon.replace("mdi:", "")
260 + }
261 +
262 + switch (entity.domain) {
263 + case "light": return entity.state === "on" ? "lightbulb-on" : "lightbulb"
264 + case "switch": return entity.state === "on" ? "toggle-switch" : "toggle-switch-off"
265 + case "fan": return "fan"
266 + case "automation": return "playlist-play"
267 + case "climate": return "thermometer"
268 + case "cover": return "window-close"
269 + case "lock": return entity.state === "locked" ? "lock" : "lock-open"
270 + case "sensor": return "gauge"
271 + case "binary_sensor": return "checkbox-marked-circle"
272 + default: return "home-assistant"
273 + }
274 + }
275 +
276 + function getStateColor(state) {
277 + switch (state) {
278 + case "on":
279 + case "open":
280 + case "home":
281 + case "unlocked":
282 + return Kirigami.Theme.positiveTextColor
283 + case "off":
284 + case "closed":
285 + case "away":
286 + case "locked":
287 + return Kirigami.Theme.disabledTextColor
288 + case "unavailable":
289 + case "unknown":
290 + return Kirigami.Theme.negativeTextColor
291 + default:
292 + return Kirigami.Theme.textColor
293 + }
294 + }
295 +
296 + Component.onCompleted: {
297 + // Auto-load entities when component is ready
298 + Qt.callLater(loadEntities)
299 + }
300 +}
added contents/ui/EntityControl.qml
+159 −0
@@ -0,0 +1,159 @@
1 +import QtQuick
2 +import QtQuick.Controls as QQC2
3 +import QtQuick.Layouts
4 +import org.kde.plasma.components as PlasmaComponents3
5 +import org.kde.kirigami as Kirigami
6 +
7 +Item {
8 + id: entityControl
9 +
10 + property string entityId: ""
11 + property string displayName: ""
12 + property string controlType: "toggle"
13 + property string iconName: "home-assistant"
14 + property bool showLabel: true
15 + property string buttonSize: "medium"
16 + property var entityState: null
17 + property bool isOn: entityState ? (entityState.state === "on" || entityState.state === "home" || entityState.state === "open") : false
18 +
19 + signal controlActivated(string entityId, string action, var data)
20 +
21 + readonly property int buttonSizePixels: {
22 + switch (buttonSize) {
23 + case "small": return Kirigami.Units.iconSizes.medium
24 + case "large": return Kirigami.Units.iconSizes.huge
25 + default: return Kirigami.Units.iconSizes.large
26 + }
27 + }
28 +
29 + width: buttonSizePixels + (showLabel ? Kirigami.Units.smallSpacing : 0)
30 + height: buttonSizePixels + (showLabel ? Kirigami.Units.gridUnit : 0)
31 +
32 + ColumnLayout {
33 + anchors.fill: parent
34 + spacing: Kirigami.Units.smallSpacing
35 +
36 + // Main control button
37 + PlasmaComponents3.Button {
38 + id: controlButton
39 + Layout.preferredWidth: buttonSizePixels
40 + Layout.preferredHeight: buttonSizePixels
41 + Layout.alignment: Qt.AlignHCenter
42 +
43 + // Visual state based on entity state
44 + property color normalColor: Kirigami.Theme.buttonBackgroundColor
45 + property color activeColor: isOn ? Kirigami.Theme.positiveBackgroundColor : normalColor
46 +
47 + background: Rectangle {
48 + radius: Kirigami.Units.cornerRadius
49 + color: controlButton.pressed ? Qt.darker(controlButton.activeColor, 1.1) :
50 + controlButton.hovered ? Qt.lighter(controlButton.activeColor, 1.1) :
51 + controlButton.activeColor
52 +
53 + border.color: isOn ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.buttonFocusColor
54 + border.width: isOn ? 2 : 1
55 +
56 + Behavior on color { ColorAnimation { duration: 150 } }
57 + Behavior on border.color { ColorAnimation { duration: 150 } }
58 + }
59 +
60 + contentItem: Kirigami.Icon {
61 + source: getIconName()
62 + color: isOn ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.textColor
63 +
64 + Behavior on color { ColorAnimation { duration: 150 } }
65 + }
66 +
67 + PlasmaComponents3.ToolTip {
68 + text: getTooltipText()
69 + }
70 +
71 + onClicked: handleControlClick()
72 + }
73 +
74 + // Entity label
75 + PlasmaComponents3.Label {
76 + visible: showLabel
77 + text: displayName
78 + Layout.fillWidth: true
79 + Layout.alignment: Qt.AlignHCenter
80 + horizontalAlignment: Text.AlignHCenter
81 + elide: Text.ElideRight
82 + font.pointSize: Kirigami.Theme.smallFont.pointSize
83 + color: isOn ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.textColor
84 +
85 + Behavior on color { ColorAnimation { duration: 150 } }
86 + }
87 + }
88 +
89 + function getIconName() {
90 + if (iconName && iconName !== "home-assistant") {
91 + return iconName
92 + }
93 +
94 + // Auto-detect icon based on entity type and state
95 + var domain = entityId.split('.')[0]
96 + switch (domain) {
97 + case 'light':
98 + return isOn ? "lightbulb-on" : "lightbulb"
99 + case 'switch':
100 + return isOn ? "toggle-switch" : "toggle-switch-off"
101 + case 'fan':
102 + return isOn ? "weather-windy" : "fan"
103 + case 'automation':
104 + return isOn ? "playlist-play" : "playlist-remove"
105 + case 'climate':
106 + return "thermometer"
107 + case 'lock':
108 + return entityState && entityState.state === "locked" ? "lock" : "lock-open"
109 + case 'cover':
110 + return entityState && entityState.state === "open" ? "window-open" : "window-close"
111 + case 'input_boolean':
112 + return isOn ? "checkbox-marked" : "checkbox-blank-outline"
113 + default:
114 + return "home-assistant"
115 + }
116 + }
117 +
118 + function getTooltipText() {
119 + var stateText = entityState ? entityState.state : "unknown"
120 + var tooltip = displayName + "\nState: " + stateText
121 +
122 + if (entityState && entityState.attributes) {
123 + if (entityState.attributes.friendly_name) {
124 + tooltip = entityState.attributes.friendly_name + "\nState: " + stateText
125 + }
126 +
127 + // Add additional info for specific entity types
128 + if (entityState.attributes.brightness) {
129 + tooltip += "\nBrightness: " + Math.round(entityState.attributes.brightness / 255 * 100) + "%"
130 + }
131 + if (entityState.attributes.temperature) {
132 + tooltip += "\nTemperature: " + entityState.attributes.temperature + "°"
133 + }
134 + }
135 +
136 + return tooltip
137 + }
138 +
139 + function handleControlClick() {
140 + switch (controlType) {
141 + case "toggle":
142 + controlActivated(entityId, "toggle", {})
143 + break
144 + case "switch":
145 + controlActivated(entityId, isOn ? "turn_off" : "turn_on", {})
146 + break
147 + case "light":
148 + // For lights, we could show a context menu or brightness slider
149 + controlActivated(entityId, "toggle", {})
150 + break
151 + case "status":
152 + // Status displays don't perform actions, just show state
153 + break
154 + default:
155 + controlActivated(entityId, "toggle", {})
156 + break
157 + }
158 + }
159 +}
added contents/ui/HomeAssistantAPI.qml
+253 −0

Click to load diff…

added contents/ui/IconPicker.qml
+276 −0

Click to load diff…

added contents/ui/configEntities.qml
+613 −0

Click to load diff…

modified contents/ui/configGeneral.qml
+121 −0

Click to load diff…

modified contents/ui/main.qml
+189 −12

Click to load diff…

modified metadata.json
+2 −1

Click to load diff…

Parents: 6924a66