From a4ae8a2f6cf6b807c3cdca292a4bd57552e25517 Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Mon, 17 Nov 2025 08:05:58 +0100 Subject: [PATCH] slider for thermostats --- apps/rules/rule_interface.py | 17 ++++ apps/ui/templates/dashboard.html | 133 +++++++++++++++++++++++++------ 2 files changed, 125 insertions(+), 25 deletions(-) diff --git a/apps/rules/rule_interface.py b/apps/rules/rule_interface.py index 47c4482..f47aa74 100644 --- a/apps/rules/rule_interface.py +++ b/apps/rules/rule_interface.py @@ -246,6 +246,23 @@ class RedisState: await self._execute_with_retry(_expire, key, ttl_secs) + async def delete(self, key: str) -> None: + """ + Delete a key from Redis. + + Args: + key: Redis key to delete + + Example: + >>> state = RedisState("redis://localhost:6379/0") + >>> await state.set("rules:r1:temp", "22.5") + >>> await state.delete("rules:r1:temp") + """ + async def _delete(client, k): + await client.delete(k) + + await self._execute_with_retry(_delete, key) + async def close(self) -> None: """ Close Redis connection and cleanup resources. diff --git a/apps/ui/templates/dashboard.html b/apps/ui/templates/dashboard.html index 6c9280a..b6cbfc9 100644 --- a/apps/ui/templates/dashboard.html +++ b/apps/ui/templates/dashboard.html @@ -365,32 +365,59 @@ color: #999; } - .temp-controls { + /* Thermostat Slider Styles */ + .thermostat-slider-control { + margin: 1rem 0; + } + + .thermostat-slider-label { display: flex; - gap: 0.5rem; - margin-bottom: 1rem; + justify-content: space-between; + align-items: center; + margin-bottom: 0.5rem; + font-size: 0.875rem; + color: #666; } - .temp-button { - flex: 1; - padding: 0.75rem; - border: none; - border-radius: 8px; - font-size: 1.125rem; - font-weight: 700; + .thermostat-slider { + width: 100%; + height: 8px; + border-radius: 4px; + background: linear-gradient(to right, #667eea 0%, #764ba2 100%); + outline: none; + -webkit-appearance: none; + appearance: none; cursor: pointer; - transition: all 0.2s; - background: #667eea; - color: white; - min-height: 44px; } - .temp-button:hover { - background: #5568d3; + .thermostat-slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 24px; + height: 24px; + border-radius: 50%; + background: white; + border: 3px solid #667eea; + cursor: pointer; + box-shadow: 0 2px 4px rgba(0,0,0,0.2); } - .temp-button:active { - transform: scale(0.95); + .thermostat-slider::-moz-range-thumb { + width: 24px; + height: 24px; + border-radius: 50%; + background: white; + border: 3px solid #667eea; + cursor: pointer; + box-shadow: 0 2px 4px rgba(0,0,0,0.2); + } + + .thermostat-slider-range { + display: flex; + justify-content: space-between; + margin-top: 0.25rem; + font-size: 0.75rem; + color: #999; } /* Contact Sensor Styles */ @@ -398,6 +425,7 @@ display: flex; align-items: center; gap: 0.75rem; + padding: 1rem; background: #f8f9fa; border-radius: 8px; @@ -965,13 +993,23 @@ -
- - +
+ + +
+ 5°C + 30°C +
{% elif device.type == "contact" or device.type == "contact_sensor" %} @@ -1327,6 +1365,7 @@ function updateThermostatUI(deviceId, current, target, mode) { const currentSpan = document.getElementById(`state-${deviceId}-current`); const targetSpan = document.getElementById(`state-${deviceId}-target`); + const slider = document.getElementById(`slider-${deviceId}`); if (current !== undefined && currentSpan) { currentSpan.textContent = current.toFixed(1); @@ -1336,6 +1375,10 @@ if (targetSpan) { targetSpan.textContent = target.toFixed(1); } + // Sync slider with actual state + if (slider) { + slider.value = target; + } thermostatTargets[deviceId] = target; } } @@ -1733,6 +1776,46 @@ } } + // Update thermostat display while dragging slider + function updateThermostatDisplay(deviceId, value) { + const targetElement = document.getElementById(`state-${deviceId}-target`); + if (targetElement) { + targetElement.textContent = `${parseFloat(value).toFixed(1)}°C`; + } + } + + // Set thermostat target temperature when slider is released + async function setThermostatTarget(deviceId, value) { + try { + const targetTemp = parseFloat(value); + + const response = await fetch(api(`/devices/${deviceId}/set`), { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + target_temperature: targetTemp + }) + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.detail || 'Request failed'); + } + + console.log(`Thermostat ${deviceId} target set to ${targetTemp}°C`); + + addEvent({ + action: 'thermostat_set', + device_id: deviceId, + target_temperature: targetTemp + }); + + } catch (error) { + console.error('Failed to set thermostat target:', error); + showToast(`Fehler beim Setzen der Zieltemperatur: ${error.message}`, 'error'); + } + } + // Execute group action async function setGroup(groupId, power, buttonElement) { const allButtons = buttonElement.parentElement.querySelectorAll('button');