Skip to content

Commit 01e388b

Browse files
Cloudoreclaude
andcommitted
Add delivery confirmation and stagger to Settings and Diagnostics UI
Settings: per-climate "Confirm delivery" switch with timeout and attempts inputs inside each managed climate row, and a global "Delivery stagger" seconds input. Diagnostics: confirmation outcome, timestamp and attempt count in the status and delivery group, new delivery status chips, the delivery_unconfirmed issue text, and attempt-of-N descriptions in the runtime log. Adds updateZoneDelivery to the API client, saveZoneDelivery to the settings controller, DeliverySettings/DeliveryConfirmationStatus types, translations for all ten languages plus the template, vitest coverage, and the regenerated integration bundle. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJhD4sYeWQvoikjYP2CxUr
1 parent 19338a9 commit 01e388b

25 files changed

Lines changed: 1894 additions & 888 deletions

‎custom_components/velair/frontend/velair-card.js‎

Lines changed: 1199 additions & 884 deletions
Large diffs are not rendered by default.

‎custom_components/velair/frontend/velair-card.js.map‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎frontend/src/velair/api/client.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { DOMAIN } from "../constants";
22
import type {
33
HomeAssistant,
44
ComfortSettings,
5+
DeliverySettings,
56
DiagnosticsSnapshot,
67
DiagnosticHistoryCategory,
78
ClimateProfileInput,
@@ -233,6 +234,17 @@ export class VelairApiClient {
233234
});
234235
}
235236

237+
public updateZoneDelivery(
238+
entityId: string,
239+
delivery: Partial<DeliverySettings>,
240+
): Promise<ScheduleResponse> {
241+
return this.hass.connection.sendMessagePromise<ScheduleResponse>({
242+
type: "velair/update_zone_delivery",
243+
entity_id: entityId,
244+
delivery,
245+
});
246+
}
247+
236248
public resetZonePreconditioningLearning(entityId: string, direction: "heat" | "cool"): Promise<ScheduleResponse> {
237249
return this.hass.connection.sendMessagePromise<ScheduleResponse>({
238250
type: "velair/reset_zone_preconditioning_learning",

‎frontend/src/velair/components/velair-card-element.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
import type {
2626
BlockDraftSource,
2727
ComfortSettings,
28+
DeliverySettings,
2829
DraftScheduleBlock,
2930
DiagnosticsSnapshot,
3031
EntityDiagnostic,
@@ -120,6 +121,7 @@ import {
120121
moveSettingsZone,
121122
saveSettings,
122123
saveZoneComfort,
124+
saveZoneDelivery,
123125
saveZonePreconditioning,
124126
resetZonePreconditioningLearning,
125127
resetZonePreconditioningSettings,
@@ -1445,6 +1447,13 @@ export class VelairCard extends LitElement {
14451447
await saveZoneComfort(asSettingsActionsHost(this), entityId, comfort);
14461448
}
14471449

1450+
private async _saveZoneDelivery(
1451+
entityId: string,
1452+
delivery: Partial<DeliverySettings>,
1453+
): Promise<void> {
1454+
await saveZoneDelivery(asSettingsActionsHost(this), entityId, delivery);
1455+
}
1456+
14481457
private _togglePreconditioningZone(entityId: string): void {
14491458
const expandedZones = new Set(this._expandedPreconditioningZones);
14501459
if (expandedZones.has(entityId)) {

‎frontend/src/velair/constants.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ export const VELAIR_LOADING_ICON_URL = "/velair_frontend/velair-icon.png";
2121
export const PROFILE_DESCRIPTION_MAX_LENGTH = 500;
2222
export const MODE_NAME_MAX_LENGTH = 255;
2323
export const DEFAULT_EXTERNAL_CHANGE_DURATION_MINUTES = 120;
24+
export const DEFAULT_DELIVERY_CONFIRM_TIMEOUT_SECONDS = 25;
25+
export const MIN_DELIVERY_CONFIRM_TIMEOUT_SECONDS = 5;
26+
export const MAX_DELIVERY_CONFIRM_TIMEOUT_SECONDS = 120;
27+
export const DEFAULT_DELIVERY_CONFIRM_ATTEMPTS = 3;
28+
export const MIN_DELIVERY_CONFIRM_ATTEMPTS = 1;
29+
export const MAX_DELIVERY_CONFIRM_ATTEMPTS = 5;
30+
export const MAX_DELIVERY_STAGGER_SECONDS = 30;
2431
export const PANEL_VIEWS: VelairPanelView[] = [
2532
"overview",
2633
"schedules",

‎frontend/src/velair/controllers/settings-actions.ts‎

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import { WEEKDAYS } from "../constants";
22
import type { VelairApiClient } from "../api/client";
3-
import type { ComfortSettings, PanelSettings, PreconditioningSettings, ScheduleResponse, VelairCardConfig } from "../types";
3+
import type {
4+
ComfortSettings,
5+
DeliverySettings,
6+
PanelSettings,
7+
PreconditioningSettings,
8+
ScheduleResponse,
9+
VelairCardConfig,
10+
} from "../types";
411

512
type SettingsActionsHost = {
613
_config: VelairCardConfig;
@@ -109,6 +116,30 @@ export async function saveZoneComfort(
109116
}
110117
}
111118

119+
export async function saveZoneDelivery(
120+
host: SettingsActionsHost,
121+
entityId: string,
122+
delivery: Partial<DeliverySettings>,
123+
): Promise<void> {
124+
const api = host._api();
125+
if (!api) {
126+
return;
127+
}
128+
129+
host._settingsSaving = true;
130+
host._error = undefined;
131+
host._saveMessage = undefined;
132+
try {
133+
const data = await api.updateZoneDelivery(entityId, delivery);
134+
host._applyScheduleData(data);
135+
host._showSuccess(host._t("deliverySettingsSaved"));
136+
} catch (error) {
137+
host._error = error instanceof Error ? error.message : host._t("unableSaveSettings");
138+
} finally {
139+
host._settingsSaving = false;
140+
}
141+
}
142+
112143
export async function resetZonePreconditioningLearning(
113144
host: SettingsActionsHost,
114145
entityId: string,

‎frontend/src/velair/host-types.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { SupportedLanguage, TranslationKey } from "./translations";
55
import type {
66
BlockDraftSource,
77
ComfortSettings,
8+
DeliverySettings,
89
DiagnosticsSnapshot,
910
DraftScheduleBlock,
1011
EntityDiagnostic,
@@ -195,6 +196,7 @@ export type VelairViewHost = {
195196
_resumeAutomaticControl(entityId: string): Promise<void>;
196197
_enterManualAdjustment(entityId: string): Promise<void>;
197198
_saveZoneComfort(entityId: string, comfort: Partial<ComfortSettings>): Promise<void>;
199+
_saveZoneDelivery(entityId: string, delivery: Partial<DeliverySettings>): Promise<void>;
198200
_saveZonePreconditioning(entityId: string, preconditioning: Partial<PreconditioningSettings>): Promise<void>;
199201
_saveTemplate(saveAsNew: boolean): Promise<void>;
200202
_scheduleTemplates(): ScheduleTemplate[];

‎frontend/src/velair/styles/settings-styles.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,14 @@ export const settingsStyles = css`
420420
padding-inline-end: 7px;
421421
}
422422
423+
.settings-delivery-confirmation .settings-policy-controls { gap: 10px; }
424+
425+
.settings-delivery-confirmation ha-switch { flex: 0 0 auto; }
426+
427+
.settings-delivery-confirmation .settings-policy-duration { flex: 0 1 96px; min-width: 72px; }
428+
429+
.settings-delivery-stagger .settings-policy-duration { flex: 0 0 96px; }
430+
423431
.settings-zone-identity strong,
424432
.settings-zone-identity span {
425433
overflow: hidden;

‎frontend/src/velair/translations/de.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ export const de = {
1515
"externalChangeForDuration": "Für eine Dauer",
1616
"externalChangeUntilResumed": "Bis zur Fortsetzung",
1717
"externalChangePolicySaved": "Richtlinie für externe Änderungen gespeichert",
18+
"deliveryConfirmation": "Zustellung bestätigen",
19+
"deliveryConfirmationDescription": "Nachdem ein Zielwert angenommen wurde, wird dieses Klimagerät beobachtet, bis es den angeforderten Modus und die Temperatur meldet. Konvergiert es nicht vor Ablauf der Zeitüberschreitung, sendet Velair die aktuelle Absicht erneut, bis zur konfigurierten Anzahl an Versuchen. Nur für Geräte aktivieren, die Befehle stillschweigend verwerfen.",
20+
"deliveryConfirmationInfoAction": "Über die Zustellbestätigung",
21+
"deliveryConfirmTimeout": "Zeitüberschreitung",
22+
"deliveryConfirmAttempts": "Versuche",
23+
"deliverySettingsSaved": "Zustelleinstellungen gespeichert",
24+
"deliveryStagger": "Zustellabstand",
25+
"deliveryStaggerDescription": "Mindestabstand in Sekunden zwischen dem Start von Befehlen an verschiedene Klimageräte. Null sendet Befehle ohne geräteübergreifende Taktung. Verwende einen kleinen Wert für Gateways, die direkt aufeinanderfolgende Schreibvorgänge verwerfen.",
1826
"manualAdjustment": "Manuelle Anpassung",
1927
"velairControl": "Velair-Steuerung",
2028
"automaticScheduling": "Automatische Planung",
@@ -691,6 +699,12 @@ export const de = {
691699
"diagnosticsDeliveryStatus": "Übertragungsstatus",
692700
"diagnosticsRetryCount": "Wiederholungen",
693701
"diagnosticsLastError": "Letzter Fehler",
702+
"diagnosticsDeliveryConfirmation": "Bestätigung",
703+
"diagnosticsDeliveryAttempts": "Bestätigungsversuche",
704+
"diagnosticsDeliveryConfirming": "Warte auf Bestätigung",
705+
"diagnosticsDeliveryConfirmed": "Vom Klimagerät bestätigt",
706+
"diagnosticsDeliveryUnconfirmed": "Das Klimagerät hat den angeforderten Zielwert nicht gemeldet.",
707+
"diagnosticsDeliveryAttemptOf": "Versuch {attempt} von {attempts}",
694708
"diagnosticsScheduleSource": "Zeitplanquelle",
695709
"diagnosticsMode": "Modus",
696710
"diagnosticsProfile": "Profil",

‎frontend/src/velair/translations/en.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ export const en = {
5959
"externalChangeForDuration": "For a duration",
6060
"externalChangeUntilResumed": "Until resumed",
6161
"externalChangePolicySaved": "External change policy saved",
62+
"deliveryConfirmation": "Confirm delivery",
63+
"deliveryConfirmationDescription": "After a target is accepted, this climate is watched until it reports the requested mode and temperature. If it does not converge before the timeout, Velair sends the current intent again, up to the configured number of attempts. Enable this only for devices that silently drop commands.",
64+
"deliveryConfirmationInfoAction": "About delivery confirmation",
65+
"deliveryConfirmTimeout": "Timeout",
66+
"deliveryConfirmAttempts": "Attempts",
67+
"deliverySettingsSaved": "Delivery settings saved",
68+
"deliveryStagger": "Delivery stagger",
69+
"deliveryStaggerDescription": "Minimum seconds between the start of commands to different climates. Zero sends commands without cross-climate pacing. Use a small value for gateways that drop back-to-back writes.",
6270
"manualAdjustment": "Manual adjustment",
6371
"velairControl": "Velair control",
6472
"automaticScheduling": "Automatic scheduling",
@@ -752,6 +760,12 @@ export const en = {
752760
"diagnosticsReasonStopped": "Stopped",
753761
"diagnosticsRetryCount": "Retries",
754762
"diagnosticsLastError": "Last error",
763+
"diagnosticsDeliveryConfirmation": "Confirmation",
764+
"diagnosticsDeliveryAttempts": "Confirmation attempts",
765+
"diagnosticsDeliveryConfirming": "Awaiting confirmation",
766+
"diagnosticsDeliveryConfirmed": "Confirmed by the climate",
767+
"diagnosticsDeliveryUnconfirmed": "The climate did not report the requested target.",
768+
"diagnosticsDeliveryAttemptOf": "Attempt {attempt} of {attempts}",
755769
"diagnosticsScheduleSource": "Schedule source",
756770
"diagnosticsMode": "Mode",
757771
"diagnosticsProfile": "Profile",

0 commit comments

Comments
 (0)