v0.8.21
This commit is contained in:
Generated
+1
-1
@@ -633,7 +633,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gree-controller"
|
||||
version = "0.8.20"
|
||||
version = "0.8.21"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aes-gcm",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "gree-controller"
|
||||
version = "0.8.20"
|
||||
version = "0.8.21"
|
||||
edition = "2021"
|
||||
authors = ["GREE Controller contributors"]
|
||||
description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support"
|
||||
|
||||
+10
-10
@@ -1,11 +1,11 @@
|
||||
d67af429e4da9ce08e9d2f2a8472849ffbd70d135b1c5da535a076026794d04c ./.env.example
|
||||
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
|
||||
343a3349537c0c0488191341d57d63ea5e1a67bd8f39fc96ad28c69b95944357 ./Cargo.lock
|
||||
c5a820ab63403ef204822f8601998f7e357e2c8c092d7398e540c100b777f492 ./Cargo.toml
|
||||
630d1a19741908e389efefb4b2db81c089c84203024c7baf0ea94754a7da2a2c ./Cargo.lock
|
||||
96e7395b59b0e6e8e7b0fd4311d33dcbb2910c756a7f3301299e92fa155b20dc ./Cargo.toml
|
||||
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
||||
0109b2ab7ad8cf745e841caf0761ad53157752935ea5937ca4c884f543aa6e84 ./README.md
|
||||
1af97e3a6eec1efa2c91f147dee689beeb33cc7efab0703c91753e056441f1aa ./README.md
|
||||
ec0f495ee6a618bdec10b260ee7838f7a872d8c5b0aa91c546226c1ad818f601 ./build.rs
|
||||
e9d12c54954d0b273b87a74ee0d5bf1100245b0c1c56b0fcc8b615d70e9b8edd ./docs/API.md
|
||||
0c47e3dbaf24ab2d7c05a55f22ab2e3c38146acf9864083ffa5b92f453f5735e ./docs/API.md
|
||||
95e3c8c99ba439a86a2fb05cfd8ef00c75cb959a024a349a082da935b726f500 ./home-assistant/README.md
|
||||
f8e8559fe10fe523ac5bc9aac25c6e26e862f679d502e8f3c39f38a0a8e40911 ./home-assistant/custom_components/gree_controller/__init__.py
|
||||
6910589f27960a28d4de9735884a7e5376e455cd885f19ce2b55947fcd135114 ./home-assistant/custom_components/gree_controller/api.py
|
||||
@@ -16,7 +16,7 @@ ab08fbe40e9bb48ebcbfff98760aaa0c9434b61b705aaaadea0c09c255d99b0e ./home-assista
|
||||
5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./home-assistant/custom_components/gree_controller/entity_map.py
|
||||
c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./home-assistant/custom_components/gree_controller/icon.png
|
||||
c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./home-assistant/custom_components/gree_controller/logo.png
|
||||
f9ce5f36c8204716d0e43933c08bf31f7b21a468abe437c9d792c2728d4a9cd8 ./home-assistant/custom_components/gree_controller/manifest.json
|
||||
99d34f75475a352410525dd395cc563160d8f8d2f85e22f8287a5c2f5b36062d ./home-assistant/custom_components/gree_controller/manifest.json
|
||||
38d659d0273c0d1428679e44574a2666c605d1b3796d323b626baa40560788cf ./home-assistant/custom_components/gree_controller/number.py
|
||||
39c4309001b75abb56234f05662bc06e077054986876f1927937edbce528ec95 ./home-assistant/custom_components/gree_controller/select.py
|
||||
1cae12876eb81085910907cf1fcf340a18dcba4f8c967ef222c9496325057849 ./home-assistant/custom_components/gree_controller/sensor.py
|
||||
@@ -25,8 +25,8 @@ f9ce5f36c8204716d0e43933c08bf31f7b21a468abe437c9d792c2728d4a9cd8 ./home-assista
|
||||
13f30e2dcdcedbd1b6c3f99c2335e0487108fd72c8e86922368b84f2fa2038ae ./home-assistant/custom_components/gree_controller/translations/pl.json
|
||||
4513070521d3dda0efb0d974a86ba674494cfb2b66fe9e5cac5b1b0430dede97 ./home-assistant/generated/gree_controller_entities.example.json
|
||||
253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md
|
||||
e627dfceebea91e7b7b01279960165020993fcb9f632117862e23d36b225c1c5 ./lang/en.json
|
||||
5b7acd24f8b0f8f20044c4657270fe8eef1097afecd6f7dbe5a4168de227c8d3 ./lang/pl.json
|
||||
4f5c278b3deb14accdcae92b25ec412da4bc5e87b86860d051b0757476afd441 ./lang/en.json
|
||||
1f5ee2f8673121f5a786c478ca4ace06917c66521a0ba79d5b52999a8d805204 ./lang/pl.json
|
||||
d8459024f04ca514bd8e9d6bd3af872fb942fd85a7cdd5583f4a9d28aab6faba ./make_zip.py
|
||||
bb89bac237e750e9b1bf73761d7df97a6b81853091615878c03f13d7b6399aa7 ./scripts/README.md
|
||||
5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh
|
||||
@@ -76,7 +76,7 @@ a384ea4042d1d11692b5f8693df1f54850dcc4e80726780b44b2b096bf667b78 ./src/db/zone_
|
||||
cb1fe22b11acdf6a593a196c4fa8f778bfe81765807f9e30416e3ca7fa9ef594 ./src/engine/groups.rs
|
||||
18a7669318949e533f206d25cf7b63771d7f0f2c0b375a72ff78a2d921baaa61 ./src/engine/history.rs
|
||||
dad8930c3d811f02cfeced3aa4b9a404fe273ba1b99539bb174b9430575f3e22 ./src/engine/local_thermostat.rs
|
||||
2110d961b20ec85356a89ed742f318e9dfca36cd61d3e9b9eb0537bb17d3271c ./src/engine/ownership.rs
|
||||
2ccaa0344609d9cacdf704293bc10a4e7e1cebc7f25a2e279f6b3b5c1798755a ./src/engine/ownership.rs
|
||||
9c37adf514c96840fd6a5c715c579350fa86d6dcc0655a2aa97d1fd7c7e04299 ./src/engine/polling.rs
|
||||
3d48608a065aa52a1fd8b9c59356a74d8216ecee3d47f8ff8ef7d3d47170c617 ./src/engine/runtime.rs
|
||||
ffa72b93502eece187a96adb4ec94aec6048d4a6dc7af66e7c7af0931318787f ./src/engine/schedules.rs
|
||||
@@ -133,7 +133,7 @@ b3d5cb473bf9771de78e14c7de730cb86c73f8cdbb6344da67ba7d6abac879ea ./web/js/boots
|
||||
64a43e2142a69ebddf4160c0d0d3b573fa379c650431090fd15c9ba3b62bc4e7 ./web/js/charts.js
|
||||
82da7a69028e159ef9b72ba32b111916e9dc21618f8c7c0e410e3492773e7c12 ./web/js/core.js
|
||||
6e421388c7f6deb2aa42f1205dfdc52839f9f178fbd672129972528b8517a870 ./web/js/dashboard.js
|
||||
362af3668dc529667edc654214c0e53911bb43f6831da274c52515e1f98a279d ./web/js/entities.js
|
||||
7ed64ca0ef62c4cd3d9c314adfcf1c3874a0c49139379dd6f175710786a2ced9 ./web/js/entities.js
|
||||
8c94a3b4aa5455638332362379b09dc151cdf47ab24774c9fd52100ecad689c5 ./web/js/events.js
|
||||
2fe6c804f86b0613a50a56c54f7a0cbee77273759edf3e6fec36ac77d35edbd7 ./web/js/forms.js
|
||||
be07a341ba4b1f514a5e13553494194b22aa87108cb04de40635a0158921780b ./web/js/history.js
|
||||
@@ -144,6 +144,6 @@ d375868ded6ffcd6b18d4c661bb2051e7cc5b136f95d4d68355cdd6e0caaf278 ./web/js/realt
|
||||
28ff1324f4495b7bcd431eb94f51012c1b1e558f8acb931ac35b71b48374c200 ./web/js/settings-ui.js
|
||||
9d44596929f67400f5f09d82330af4452914f2b4729eadcd4f695672d2cd849c ./web/js/settings.js
|
||||
fd26156e9f1d6713d3def564ad000553d9a16a24376059701db2ee762c99ee6c ./web/manifest.webmanifest
|
||||
ce1c0ba9ebc5307a61548cb5faa36f9d1129df45075fc86ad7c7ff4179830281 ./web/styles.css
|
||||
e97553a992815ba49830514b0f1eb4d686f2097889574e9da6741af90b7ab5eb ./web/styles.css
|
||||
03ac3a5918539904ae829ee24999d8d59efb41bdf6c0e30f76bab4cee98c3130 ./web/sw.js
|
||||
d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Self-hosted controller for GREE-compatible air conditioners with a local Web UI, thermostat zones, schedules, Home Assistant integration, history, notifications and a documented HTTP/WebSocket API.
|
||||
|
||||
**Current release: 0.8.20**
|
||||
**Current release: 0.8.21**
|
||||
|
||||
> [Full API reference](docs/API.md) — authentication, every endpoint, request bodies, response models, WebSocket events and examples.
|
||||
|
||||
|
||||
+5
-3
@@ -1,6 +1,6 @@
|
||||
# GREE Controller API reference
|
||||
|
||||
HTTP and WebSocket API for GREE Controller **0.8.20**.
|
||||
HTTP and WebSocket API for GREE Controller **0.8.21**.
|
||||
|
||||
[← Main documentation](../README.md)
|
||||
|
||||
@@ -212,7 +212,7 @@ Response:
|
||||
{
|
||||
"status": "ok",
|
||||
"name": "gree-controller",
|
||||
"version": "0.8.20",
|
||||
"version": "0.8.21",
|
||||
"uptime_seconds": 1234,
|
||||
"control_ready": true,
|
||||
"time": "2026-08-30T06:54:00Z"
|
||||
@@ -236,7 +236,7 @@ Returns the initial Web UI snapshot:
|
||||
"settings": {},
|
||||
"outdoor_temperature": null,
|
||||
"system": {
|
||||
"version": "0.8.20",
|
||||
"version": "0.8.21",
|
||||
"uptime_seconds": 1234,
|
||||
"auth_required": false,
|
||||
"control_ready": true,
|
||||
@@ -669,6 +669,8 @@ All fields optional:
|
||||
|
||||
A custom setpoint creates the same temporary manual override for every member zone and normally expires at that zone's next schedule boundary. Returns a group control/result object including updated members/state.
|
||||
|
||||
Group power is a per-group gate. Whole-house `power=false` closes all group gates. If one group is later switched back on, only that group is reopened; other groups remain off and their own member zones stay disabled. A zone is never blocked merely because a different, unrelated group is on. If a zone belongs to multiple groups, any disabled membership remains authoritative.
|
||||
|
||||
---
|
||||
|
||||
## Whole-house control
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "gree_controller",
|
||||
"name": "GREE Controller",
|
||||
"version": "0.8.20",
|
||||
"version": "0.8.21",
|
||||
"config_flow": true,
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
|
||||
+3
-1
@@ -776,6 +776,8 @@
|
||||
"groups.customDraftPending": "Not applied. The group changes profile only after you press Apply.",
|
||||
"zones.waitingOffline": "Waiting — unit offline or communication error",
|
||||
"zones.waitingDeviceDisabled": "Waiting — device is technically disabled",
|
||||
"zones.waitingMode": "Waiting for the unit mode to change"
|
||||
"zones.waitingMode": "Waiting for the unit mode to change",
|
||||
"groups.gateOff": "Group off",
|
||||
"zones.disabledByGroup": "Disabled by group: {groups}"
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -776,6 +776,8 @@
|
||||
"groups.customDraftPending": "Nie zastosowano. Grupa zmieni profil dopiero po kliknięciu „Zastosuj”.",
|
||||
"zones.waitingOffline": "Oczekuje — jednostka offline lub błąd komunikacji",
|
||||
"zones.waitingDeviceDisabled": "Oczekuje — urządzenie jest wyłączone technicznie",
|
||||
"zones.waitingMode": "Oczekuje na zmianę trybu jednostki"
|
||||
"zones.waitingMode": "Oczekuje na zmianę trybu jednostki",
|
||||
"groups.gateOff": "Grupa wyłączona",
|
||||
"zones.disabledByGroup": "Wyłączona przez grupę: {groups}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@ pub fn reset_device_manual_override(zone: &mut Zone) -> bool {
|
||||
changed
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn manual_override_matches_baseline(zone: &Zone, device: &Device) -> bool {
|
||||
let Some(baseline) = zone.device_manual_override_baseline.as_ref() else { return false; };
|
||||
if zone.device_manual_override_fields.is_empty() { return false; }
|
||||
|
||||
+12
-6
@@ -138,7 +138,7 @@ function zoneRuntimeStatusLabel(zone, effectiveMode, device = null) {
|
||||
if (!zone.enabled) return tr('common.disabled');
|
||||
if (zone.local_thermostat_power === false) return tr('zones.localThermostatOff');
|
||||
const blockers=blockingGroupsForZone(zone);
|
||||
if (blockers.length) return tr('zones.waitingGroup', {groups:blockers.map(group=>group.name).join(', ')});
|
||||
if (blockers.length) return tr('zones.disabledByGroup', {groups:blockers.map(group=>group.name).join(', ')});
|
||||
if ((effectiveMode || 'off') === 'off') return tr('zones.waiting');
|
||||
if (device?.enabled === false) return tr('zones.waitingDeviceDisabled');
|
||||
if (device && (!device.online || Number(device.communication_failures || 0) > 0)) return tr('zones.waitingOffline');
|
||||
@@ -172,6 +172,8 @@ function zoneCard(zone, detailed = true) {
|
||||
const startWaiting = zone.demand && !!device && !deviceUnavailable && !device.power;
|
||||
const modeWaiting = zone.demand && !!device?.power && !deviceUnavailable && ['heat','cool'].includes(effectiveMode) && device.mode !== effectiveMode;
|
||||
const groupBlockedWaiting = blockingGroups.length > 0;
|
||||
const singleBlockingGroup = blockingGroups.length === 1 ? blockingGroups[0] : null;
|
||||
const visualGroup = controlGroup || singleBlockingGroup;
|
||||
const waitingDemand = lockoutWaiting || startWaiting || modeWaiting || groupBlockedWaiting || (zone.demand && deviceUnavailable);
|
||||
const workingDemand = zone.demand && !!device?.power && !deviceUnavailable && !waitingDemand;
|
||||
const localThermostatPower = zone.local_thermostat_power;
|
||||
@@ -208,7 +210,7 @@ function zoneCard(zone, detailed = true) {
|
||||
const groupNames = (app.groups || []).filter(group => (group.zone_ids || []).includes(zone.id)).map(group => group.name);
|
||||
const groupText = groupNames.length ? groupNames.join(' · ') : tr('zones.noGroup');
|
||||
const policy = zone.inherit_house_mode ? tr('zones.followHouse') : tr(zone.mode === 'heat' ? 'zones.heatOnly' : 'zones.coolOnly');
|
||||
return `<article class="list-card zone-config-card ${workingDemand ? 'demanding' : ''} ${waitingDemand ? 'lockout-waiting' : ''} ${zone.enabled ? '' : 'zone-disabled'} ${deviceManualOverride ? 'manual-takeover' : ''} ${controlGroup ? 'group-controlled' : ''}" data-zone-config="${esc(zone.id)}"${groupControlStyle(controlGroup)}>
|
||||
return `<article class="list-card zone-config-card ${workingDemand ? 'demanding' : ''} ${waitingDemand ? 'lockout-waiting' : ''} ${zone.enabled ? '' : 'zone-disabled'} ${deviceManualOverride ? 'manual-takeover' : ''} ${controlGroup ? 'group-controlled' : ''} ${singleBlockingGroup ? 'group-blocked' : ''}" data-zone-config="${esc(zone.id)}"${groupControlStyle(visualGroup)}>
|
||||
<div class="list-card-head"><div><span class="eyebrow">${esc(tr('zones.configuration'))}</span><h3>${esc(zone.name)}</h3><p>${esc(device?.name || tr('common.noDevice'))} · ${esc(tr('groups.group'))}: ${esc(groupText)}</p></div><button type="button" class="zone-enable-toggle ${zone.enabled ? 'active' : ''}" data-action="zone-enabled" data-id="${esc(zone.id)}" data-value="${zone.enabled ? 'false' : 'true'}" aria-label="${esc(tr(zone.enabled ? 'zones.disable' : 'zones.enable'))}" title="${esc(tr('zones.automationToggleHint'))}"><span>${zone.enabled ? '✓' : '○'}</span>${esc(state)}</button></div>
|
||||
<div class="zone-config-status">
|
||||
<div class="zone-config-temperature"><small>${esc(tr('zones.currentStatus'))}</small><div><span>${fmtTemp(roomTemperature)}</span><b>→</b><strong>${Number.isFinite(target) ? `${target.toFixed(1)}°C` : '—'}</strong></div></div>
|
||||
@@ -226,14 +228,15 @@ function zoneCard(zone, detailed = true) {
|
||||
</article>`;
|
||||
}
|
||||
|
||||
return `<article class="list-card zone-thermostat quick-control-card quick-thermostat-control ${workingDemand ? 'demanding' : ''} ${waitingDemand ? 'lockout-waiting' : ''} ${zone.enabled ? '' : 'zone-disabled'} ${deviceManualOverride ? 'manual-takeover' : ''} ${controlGroup ? 'group-controlled' : ''}" data-zone-card="${esc(zone.id)}"${groupControlStyle(controlGroup)}>
|
||||
const runtimeStatus = zoneRuntimeStatusLabel(zone, effectiveMode, device);
|
||||
return `<article class="list-card zone-thermostat quick-control-card quick-thermostat-control ${workingDemand ? 'demanding' : ''} ${waitingDemand ? 'lockout-waiting' : ''} ${zone.enabled ? '' : 'zone-disabled'} ${deviceManualOverride ? 'manual-takeover' : ''} ${controlGroup ? 'group-controlled' : ''} ${singleBlockingGroup ? 'group-blocked' : ''}" data-zone-card="${esc(zone.id)}"${groupControlStyle(visualGroup)}>
|
||||
<div class="list-card-head"><div><h3>${esc(zone.name)}</h3><p>${esc(device?.name || tr('common.noDevice'))} · ${esc(zonePresetLabel(displayPreset))}</p></div><div class="zone-quick-actions">${localThermostatPowerControl}${temporaryThermostatControl}</div></div>
|
||||
<div class="thermostat-main"><div><small>${esc(tr('zones.measurement'))}</small><strong>${fmtTemp(roomTemperature)}</strong></div><div class="temperature-control compact"><button data-action="zone-temperature" data-id="${esc(zone.id)}" data-delta="-0.5">−</button><div class="target-temp compact editable-target" data-temperature-kind="zone" data-id="${esc(zone.id)}" data-value="${Number.isFinite(target) ? target : ''}" tabindex="0" role="button" title="${esc(tr('common.clickTemperatureToEdit'))}">${Number.isFinite(target)?target.toFixed(1):'--'}<small>°C</small></div><button data-action="zone-temperature" data-id="${esc(zone.id)}" data-delta="0.5">+</button></div><div><small>${esc(tr('zones.deviceTarget'))}</small><strong>${fmtTemp(zone.device_setpoint)}</strong></div></div>
|
||||
<div class="preset-row quick-control-row quick-control-row-4">
|
||||
${['auto','comfort','sleep','away'].map(preset=>`<button class="${manual===preset?'active':''}" data-action="zone-preset" data-id="${esc(zone.id)}" data-value="${preset}">${esc(preset==='sleep'?tr('zones.sleepNow'):zonePresetLabel(preset))}</button>`).join('')}
|
||||
</div>
|
||||
<div class="mode-row zone-mode-row quick-control-row quick-control-row-3"><button class="${mode==='house'&&globalModeAvailable?'active':''}" data-action="zone-mode" data-id="${esc(zone.id)}" data-value="house"${globalModeDisabled}>${esc(tr('zones.followHouseShort'))}</button><button class="${mode==='heat'?'active':''}" data-action="zone-mode" data-id="${esc(zone.id)}" data-value="heat">${esc(modeLabel('heat'))}</button><button class="${mode==='cool'?'active':''}" data-action="zone-mode" data-id="${esc(zone.id)}" data-value="cool">${esc(modeLabel('cool'))}</button></div>
|
||||
<div class="zone-state-line"><span>${esc(zoneRuntimeStatusLabel(zone, effectiveMode, device))}${controlGroup ? ` <b class="group-control-tag">${esc(controlGroup.name)}</b>` : ''}</span><span>${esc(override)}</span></div>
|
||||
<div class="zone-state-line"><span class="zone-runtime-status ${groupBlockedWaiting ? 'group-blocked-status' : ''}" title="${esc(runtimeStatus)}">${esc(runtimeStatus)}${controlGroup ? ` <b class="group-control-tag">${esc(controlGroup.name)}</b>` : ''}</span><span>${esc(override)}</span></div>
|
||||
<div class="zone-state-line control-owner-line"><span><strong>${esc(tr('zones.controlOwner'))}:</strong> ${esc(zoneControlOwnerLabel(zone))}</span><span>${esc(zoneControlOwnerMeta(zone))}</span></div>
|
||||
${manualTakeover}
|
||||
</article>`;
|
||||
@@ -288,8 +291,11 @@ function groupCard(group, detailed = false) {
|
||||
const customEditorVisible = state.preset === 'custom' || customDraft?.open === true;
|
||||
const customDraftPending = customDraft?.open === true && state.preset !== 'custom';
|
||||
const groupIsControlSource = powerEnabled && state.zones.some(zone => String(zone.control_source || '') === `group:${group.id}` && !zone.device_manual_override && zone.local_thermostat_power == null);
|
||||
return `<article class="list-card group-card ${powerEnabled ? '' : 'group-off'} ${groupIsControlSource ? 'group-controlled' : ''}"${groupControlStyle(groupIsControlSource ? group : null)}>
|
||||
<div class="list-card-head"><div><span class="eyebrow">${esc(tr('groups.group'))}</span><h3>${esc(group.name)}</h3><p>${esc(memberText)}</p></div>${groupIsControlSource ? `<span class="group-control-badge">${esc(tr('groups.activeControl'))}</span>` : ''}</div>
|
||||
const groupStatusBadge = groupIsControlSource
|
||||
? `<span class="group-control-badge">${esc(tr('groups.activeControl'))}</span>`
|
||||
: (!powerEnabled ? `<span class="group-control-badge group-off-badge">${esc(tr('groups.gateOff'))}</span>` : '');
|
||||
return `<article class="list-card group-card group-linked ${powerEnabled ? '' : 'group-off'} ${groupIsControlSource ? 'group-controlled' : ''}"${groupControlStyle(group)}>
|
||||
<div class="list-card-head"><div><span class="eyebrow">${esc(tr('groups.group'))}</span><h3>${esc(group.name)}</h3><p>${esc(memberText)}</p></div>${groupStatusBadge}</div>
|
||||
${detailed ? `<div class="group-state-summary"><div><small>${esc(tr('groups.mode'))}</small><strong>${esc(modeLabelForGroup(state.mode))}</strong></div><div><small>${esc(tr('groups.profile'))}</small><strong>${esc(presetLabelForGroup(state.preset))}</strong></div><div><small>${esc(tr('groups.members'))}</small><strong>${state.zones.length}</strong></div></div>` : ''}
|
||||
<div class="group-control-block"><small>${esc(tr('common.power'))}</small><div class="group-button-row"><button class="${powerEnabled ? 'active' : ''}" data-action="group-power" data-id="${esc(group.id)}" data-value="true">${esc(tr('common.on'))}</button><button class="${!powerEnabled ? 'active' : ''}" data-action="group-power" data-id="${esc(group.id)}" data-value="false">${esc(tr('common.off'))}</button></div></div>
|
||||
<div class="group-control-block"><small>${esc(tr('groups.mode'))}</small><div class="mode-row group-mode-row">${['house','heat','cool'].map(mode => `<button class="${state.mode === mode ? 'active' : ''}" data-action="group-mode" data-id="${esc(group.id)}" data-value="${mode}">${esc(mode === 'house' ? tr('groups.followHouse') : modeLabel(mode))}</button>`).join('')}</div></div>
|
||||
|
||||
@@ -1385,3 +1385,34 @@ textarea[aria-invalid="true"] {
|
||||
.group-custom-temperature-row > span { grid-column:2; }
|
||||
.group-custom-temperature-row button { grid-column:auto; }
|
||||
}
|
||||
|
||||
/* v0.8.21: make group gates visually unambiguous. */
|
||||
.list-card.group-card.group-linked {
|
||||
border-color:color-mix(in srgb,var(--group-control-color) 42%,var(--line));
|
||||
}
|
||||
.list-card.group-card.group-off {
|
||||
border-style:dashed;
|
||||
border-color:color-mix(in srgb,var(--group-control-color) 62%,var(--line));
|
||||
}
|
||||
.group-off-badge {
|
||||
color:var(--group-control-color);
|
||||
border:1px dashed color-mix(in srgb,var(--group-control-color) 55%,var(--line));
|
||||
background:color-mix(in srgb,var(--group-control-color) 7%,var(--surface));
|
||||
}
|
||||
.zone-thermostat.group-blocked,.zone-config-card.group-blocked {
|
||||
border-color:var(--group-control-color);
|
||||
border-style:dashed;
|
||||
box-shadow:0 0 0 1px color-mix(in srgb,var(--group-control-color) 25%,transparent);
|
||||
}
|
||||
.quick-thermostat-control .zone-state-line .group-blocked-status {
|
||||
white-space:normal;
|
||||
overflow:visible;
|
||||
text-overflow:clip;
|
||||
color:color-mix(in srgb,var(--group-control-color) 78%,var(--text));
|
||||
font-weight:700;
|
||||
}
|
||||
.list-card.group-card.group-linked.group-controlled {
|
||||
border-style:solid;
|
||||
border-color:var(--group-control-color);
|
||||
box-shadow:0 0 0 1px color-mix(in srgb,var(--group-control-color) 30%,transparent);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user