Velair exposes WebSocket commands for the sidebar panel and optional Lovelace card.
Frontend code should use Home Assistant's WebSocket connection:
await hass.connection.sendMessagePromise({
type: "velair/get_schedule",
});Most write commands return the full schedule response:
The response includes a runtime-only zone_runtime mapping. It is derived by the backend and never persisted. Each managed climate reports an authoritative state (stopped, paused, boost, preconditioning, scheduled, idle, or externally_managed) plus the minimal available room, target, applied setpoint, HVAC mode, and relevant timing fields used by Overview.
{
"configured_entities": ["climate.living_room"],
"temperature_unit": "°C",
"home_assistant_temperature_unit": "°C",
"temperature_migration": {
"required": false,
"reason": null,
"source_unit": "°C",
"target_unit": "°C",
"temperature_revision": 3
},
"operation_recovery": null,
"global": {
"mode": "auto",
"paused_started_at": null,
"paused_until": null,
"active_profile_ids": ["away"],
"active_mode_id": "away-mode"
},
"active_profile_ids": ["away"],
"profiles": [
{
"key": "away",
"name": "Away",
"icon": "mdi:home-export-outline",
"color": "#546e7a",
"description": "Lower demand while nobody is home",
"zones": {
"climate.living_room": {
"behavior": "schedule",
"schedule": {
"monday": []
}
}
}
}
],
"modes": [
{
"key": "away-mode",
"name": "Away",
"profile_ids": ["away"]
}
],
"active_mode_id": "away-mode",
"settings": {
"first_weekday": "monday",
"zone_order": ["climate.living_room"],
"min_temperature": 5,
"max_temperature": 35,
"apply_active_schedule_on_startup": false
},
"zones": {
"climate.living_room": {
"enabled": true,
"schedule": {
"monday": [
{
"start": "06:00",
"action": "set_temperature",
"target_temp_low": 20,
"target_temp_high": 24,
"hvac_mode": "heat_cool",
"fan_mode": "quiet",
"preset_mode": "eco",
"swing_mode": "vertical",
"swing_horizontal_mode": "left",
"humidity": 45
}
]
},
"override": null,
"preconditioning": {
"enabled": false,
"max_lead_minutes": 1440,
"minimum_delta_temperature": 0.3,
"learning_history_size": 120,
"similar_sample_count": 25,
"comfort_percentile": 80,
"adaptive_percentile_enabled": true,
"partial_expiry_days": 30,
"recency_decay_days": 30,
"min_start_minutes": 10,
"fallback_minutes_per_degree": 25,
"use_outdoor_temperature": true,
"outdoor_temperature_entity_id": "sensor.outdoor_temperature",
"room_temperature_entity_id": "sensor.living_room_temperature",
"room_sensor_assist_enabled": false,
"room_sensor_assist_deadband": 0.3,
"room_sensor_assist_max_delta": 2.0
},
"comfort": {
"enabled": false,
"temperature_entity_id": null,
"humidity_enabled": true,
"humidity_entity_id": null,
"co2_entity_id": null,
"temperature_min": 20.0,
"temperature_max": 24.0,
"humidity_min": 40.0,
"humidity_max": 60.0,
"co2_attention": 1000,
"co2_poor": 1500,
"stale_after_minutes": 120
}
}
},
"operational_status": "scheduled",
"next_event": null,
"next_events": [],
"active_overrides": {},
"room_sensor_assist": {
"climate.living_room": {
"status": "assisting",
"enabled": true,
"configured": true,
"room_temperature_entity_id": "sensor.living_room_temperature",
"target_temperature": null,
"target_temp_low": 20,
"target_temp_high": 24,
"applied_temperature": null,
"applied_target_temp_low": 21,
"applied_target_temp_high": 25,
"climate_target_temperature": null,
"climate_target_temp_low": 21,
"climate_target_temp_high": 25,
"room_temperature": 19.8,
"climate_temperature": 20.5,
"assist_delta": 0.2,
"applied_offset": null,
"range_shift": 1.0,
"limited_by": null,
"limit_temperature": null,
"requested_temperature": null,
"calculated_temperature": null,
"scheduled_target_guard": null,
"requested_target_temp_low": null,
"requested_target_temp_high": null,
"direction": "heat",
"hvac_mode": "heat_cool",
"weekday": "monday",
"start": "06:00"
}
},
"comfort": {
"climate.living_room": {
"enabled": false,
"condition": "monitoring_off",
"air_quality": "not_monitored",
"data_quality": "unavailable",
"data_issues": []
}
},
"zone_runtime": {
"climate.living_room": {
"state": "scheduled",
"room_temperature": 19.8,
"target_temperature": null,
"target_temp_low": 20,
"target_temp_high": 24,
"applied_temperature": null,
"hvac_mode": "heat_cool",
"active_from": "2026-05-19T06:00:00+00:00",
"target_when": null
}
},
"preconditioning_learning": {
"climate.living_room": {
"status": "learning",
"required_samples": 5,
"total_samples": 0,
"heat": {
"status": "learning",
"sample_count": 0,
"total_samples": 0,
"required_samples": 5,
"effective_lead_minutes": null,
"effective_lead_source": "initial_model",
"partial_sample_count": 0,
"complete_sample_count": 0,
"invalid_sample_count": 0,
"lead_limited_by_max": false,
"last_quality": null,
"model_source": "initial_model",
"comfort_percentile": 80,
"similar_sample_count": 25
},
"cool": {
"status": "learning",
"sample_count": 0,
"total_samples": 0,
"required_samples": 5,
"effective_lead_minutes": null,
"effective_lead_source": "initial_model",
"partial_sample_count": 0,
"complete_sample_count": 0,
"invalid_sample_count": 0,
"lead_limited_by_max": false,
"last_quality": null,
"model_source": "initial_model",
"comfort_percentile": 80,
"similar_sample_count": 25
}
}
},
"templates": [],
"versions": {
"export_format": "velair_portable_data",
"portable_model": 8,
"storage": 1,
"model": 7,
"integration": "1.7.0"
}
}operation_recovery is normally null. When persisted data could not be fully
published back into runtime state, it contains operation, phase, persisted,
and message; the scheduler remains stopped until the integration reloads or
Home Assistant restarts. Runtime zone_runtime, Room Assist, and Comfort
projections are suppressed while a temperature or recovery block is active.
temperature_unit identifies the unit of the stored Velair values.
home_assistant_temperature_unit is the unit currently detected from Home
Assistant. Both are informational and cannot be changed through Velair. When
they differ, temperature_migration.required is true, operational_status
is temperature_migration_required, automatic scheduling and Velair write
operations remain blocked, and the frontend must ask the user to migrate the
stored values.
There is one upgrade exception. A response with
temperature_migration.reason equal to
legacy_celsius_upgrade_reset_required identifies published Celsius-only data
loaded while Home Assistant uses Fahrenheit. The frontend must offer
velair/reset_data, not velair/resolve_temperature_migration. Reset replaces
the legacy model atomically with defaults in Home Assistant's current unit.
Subsequent unit changes use home_assistant_unit_changed and the explicit
migration command below.
await hass.connection.sendMessagePromise({
type: "velair/resolve_temperature_migration",
source_unit: "°C",
migration_id: crypto.randomUUID(),
expected_revision: 3,
});Available only while the stored unit differs from Home Assistant. The source
must match temperature_migration.source_unit; the target is always
temperature_migration.target_unit. The command converts all thermal
configuration and learning data, persists it atomically, dismisses the
notification, starts the scheduler, and returns the full response. The
migration id makes exact retries harmless and expected_revision rejects stale
clients.
next_event is the scheduler's earliest due action. next_events is the UI-oriented list of the next visible event per managed climate, sorted by apply time; preconditioning events include target_when so the panel can show both the early start and the comfort target time.
Preconditioned native ranges remain an exclusive target union: serialized
events include both target_temp_low and target_temp_high and leave
temperature null. Their diagnostics expose target_kind: "range", the
effective target_boundary (low or high), and boundary_temperature.
Scalar events retain target_kind: "scalar" and target_boundary: "temperature".
preconditioning_learning is local runtime/storage status used by the panel. It can be included explicitly in portable exports and is never sent outside Home Assistant by Velair. Direction statuses are learning, ready, or unsupported when the climate does not report a compatible HVAC mode. A direction is ready when it has at least 5 complete local samples; before that, Adaptive predictions use the initial event-specific model.
The actual lead is calculated per future event from the current temperature delta and the selected local model source (initial_model or history).
Stored observations are trimmed per climate direction. learning_history_size limits useful complete and partial samples, while only the 10 newest invalid diagnostic samples are retained separately. Invalid samples cannot evict useful learning history. Heat and cool keep separate local histories, so seasonal cooling samples cannot evict heating samples, and heating samples cannot evict cooling samples.
Range observations use the effective boundary in the existing target_temp
field and may add target_temp_low, target_temp_high, and target_boundary.
These fields are optional so existing stored and portable observations remain
valid. Temperature migration converts both optional boundaries as absolute
temperatures.
comfort is the local runtime Environmental Comfort assessment. It contains the human environmental condition, independent CO2 air_quality, data_quality, data_issues, and raw metric payloads. Opening or refreshing the panel does not emit comfort automation events.
await hass.connection.sendMessagePromise({
type: "velair/get_schedule",
});When a compatible provider is detected, the response also includes
external_execution.systems with provider capabilities and eligible entity IDs,
plus external_execution.zones with the configured provider, availability, and
nullable runtime publication. Publication is null until Velair attempts one
in the current runtime; an attempt can be publishing, published, or failed.
published means the provider service accepted the call. It is not a hardware
verification claim. Availability is reported separately. Selected registered
providers retain their name and capabilities if they later become unavailable;
unknown persisted provider keys remain safe and unavailable. Publication state
is not persisted, read back, retried, or polled.
Schedule capabilities are provider-neutral: supports_profile_schedules,
supported_actions, supported_hvac_modes, supported_target_types,
supported_option_fields, max_switchpoints_per_day, and
time_step_minutes. Providers can also declare
implicit_midnight_change_counts_toward_limit when the continuity switchpoint
Velair adds at 00:00 consumes one of the daily changes. Clients must derive controller conditions from these
fields rather than branching on a provider key. The ramses_cc provider declares
set_temperature, heat, scalar, and no supported climate option fields.
await hass.connection.sendMessagePromise({
type: "velair/set_zone_execution",
entity_id: "climate.living_room",
provider: "ramses_cc",
});Both entity_id and provider are required. Set provider explicitly to
null to return the zone to local Velair execution. External ownership is
persisted before publication and remains external if publication fails. While
external, all Velair climate actions are rejected. The scheduler resolves and
publishes the effective full week: Default for normal Profile behavior, or the
Profile schedule for schedule behavior. Profile pause behavior is rejected for
external zones. Profile and Mode selection is persisted before publication; a
provider failure does not roll the selection back. Re-selecting Default, a
Profile, or a Mode explicitly republishes the effective week when runtime
publication evidence is failed or absent; no background retry is implied.
The result is the full schedule response.
Diagnostics uses a separate read-only snapshot so opening the view cannot change scheduler state. History contains at most 100 sanitized runtime events; the category selection is persisted, but the events themselves are cleared whenever Velair or Home Assistant restarts.
const diagnostics = await hass.connection.sendMessagePromise({
type: "velair/get_diagnostics",
});The request has no fields beyond type. The result has this top-level schema:
interface DiagnosticsSnapshot {
generated_at: string;
history_limit: number;
history_policy: {
categories: Record<DiagnosticsCategory, boolean>;
runtime_only: true;
cleared_on_restart: true;
};
overall: {
status: "ok" | "warning" | "error";
scheduler_mode: string;
scheduler_status: string;
unit_counts: { ok: number; warning: number; error: number };
issues: Array<Record<string, unknown>>;
};
units: Record<string, Record<string, unknown>>;
history: Array<Record<string, unknown>>;
}
type DiagnosticsCategory =
| "control"
| "room_assist"
| "preconditioning"
| "comfort"
| "delivery"
| "availability";The command returns not_loaded when the integration runtime is unavailable.
const report = await hass.connection.sendMessagePromise({
type: "velair/export_diagnostics",
redact_entity_ids: true,
});redact_entity_ids is an optional boolean and defaults to true. The result is
{ privacy, diagnostics }: diagnostics has the snapshot schema above and
privacy states whether entity IDs were replaced, confirms that operational
Profile, Mode, and pause identifiers are always removed, and reminds callers
that history is runtime-only and the report must be reviewed before sharing.
Setting redact_entity_ids to false keeps local entity IDs but does not
disable operational-identifier redaction. The command returns not_loaded if
Velair is unavailable; a non-boolean option is rejected by WebSocket schema
validation.
const diagnostics = await hass.connection.sendMessagePromise({
type: "velair/update_diagnostics_history",
enabled_categories: ["control", "room_assist", "delivery"],
});enabled_categories is a required list containing only the six
DiagnosticsCategory values above. The exact enabled set is persisted in the
entry-specific diagnostics policy store. Events belonging to a category that
is disabled are immediately removed from the in-memory history, and future
events in that category are not retained. Current health calculation and
automation events are unaffected. The result is the refreshed
DiagnosticsSnapshot. Invalid category names or a missing list fail WebSocket
schema validation; not_loaded means the integration is unavailable. A policy
storage failure is returned as a command failure and leaves the previous
in-memory policy active.
const diagnostics = await hass.connection.sendMessagePromise({
type: "velair/clear_diagnostics_history",
});The request has no additional fields. It clears only the bounded in-memory
event list and returns the refreshed DiagnosticsSnapshot. It does not change
enabled categories, current health evidence, schedules, or climate control.
The command returns not_loaded when Velair is unavailable.
const unsubscribe = await hass.connection.subscribeMessage(
(message) => {
if (message.loaded) {
renderDiagnostics(message.diagnostics);
}
},
{ type: "velair/subscribe_diagnostics" },
);The request has no additional fields. Successful registration is acknowledged
first, followed immediately by an event shaped as
{ loaded: true, diagnostics: DiagnosticsSnapshot }. Later lightweight
diagnostic revisions use the same event shape. If the integration unloads, the
subscription receives { loaded: false }; if it is already unavailable when
subscribing, registration returns not_loaded.
const unsubscribe = await hass.connection.subscribeMessage(
(message) => {
if (message.loaded && message.schedule) {
// Re-render with message.schedule.
}
},
{
type: "velair/subscribe_updates",
},
);The integration sends an event whenever scheduler state changes. The event contains the full schedule response under schedule.
await hass.connection.sendMessagePromise({
type: "velair/set_daily_schedule",
entity_id: "climate.living_room",
weekday: "monday",
blocks: [
{
start: "06:00",
action: "set_temperature",
target_temp_low: 20,
target_temp_high: 24,
hvac_mode: "heat_cool",
fan_mode: "quiet",
preset_mode: "eco"
},
{ start: "23:30", action: "turn_off" }
],
});If action is omitted, the backend treats the block as set_temperature for compatibility with older schedules.
Temperature blocks contain either temperature, or the complete
target_temp_low and target_temp_high pair. The two representations are
mutually exclusive. Blocks may also include optional climate settings:
fan_mode, preset_mode, swing_mode, swing_horizontal_mode, and
humidity. The scheduler filters these fields against the target climate
capabilities before persisting or applying them. Unsupported fields are
dropped; turn_off blocks never keep target or optional climate settings.
await hass.connection.sendMessagePromise({
type: "velair/copy_day_schedule",
entity_id: "climate.living_room",
source_weekday: "monday",
target_weekdays: ["tuesday", "wednesday"],
});Clear one weekday:
await hass.connection.sendMessagePromise({
type: "velair/clear_schedule",
entity_id: "climate.living_room",
weekday: "monday",
});Clear all weekdays for one zone:
await hass.connection.sendMessagePromise({
type: "velair/clear_schedule",
entity_id: "climate.living_room",
});Create a template:
await hass.connection.sendMessagePromise({
type: "velair/set_schedule_template",
name: "Evening",
blocks: [
{
start: "18:00",
action: "set_temperature",
temperature: 21,
hvac_mode: "heat",
fan_mode: "quiet"
},
{ start: "23:00", action: "set_temperature", temperature: 17 }
],
});Update a template by passing its key:
await hass.connection.sendMessagePromise({
type: "velair/set_schedule_template",
key: "evening",
name: "Evening",
blocks: [
{ start: "18:00", action: "set_temperature", temperature: 20 }
],
});Delete a template:
await hass.connection.sendMessagePromise({
type: "velair/delete_schedule_template",
key: "evening",
});await hass.connection.sendMessagePromise({
type: "velair/update_settings",
first_weekday: "sunday",
zone_order: ["climate.living_room"],
apply_active_schedule_on_startup: true
});Templates are capability-neutral storage. They can contain optional climate settings from any managed climate. Filtering happens later when a template is applied to one concrete climate schedule.
await hass.connection.sendMessagePromise({
type: "velair/update_external_change_policy",
entity_id: "climate.living_room",
policy: "for_duration",
duration_minutes: 120,
});entity_id is required and must be managed. policy is required and accepts
keep_automatic, until_next_block, for_duration, or until_resumed.
duration_minutes is optional, is meaningful for for_duration, and must be
an integer from 1 through 10080. The policy is the default for future external
changes; updating it does not alter an already active Manual adjustment. The
result is the full schedule response.
Errors are not_loaded, temperature_migration_required,
operation_in_progress, or invalid_external_change_policy; malformed fields
can also fail WebSocket schema validation.
await hass.connection.sendMessagePromise({
type: "velair/enter_manual_adjustment",
entity_id: "climate.living_room",
});The only parameter is the required managed entity_id. Velair captures the
live HVAC mode and scalar or native-range target, then starts a persisted
Manual adjustment using that zone's saved policy. If the saved policy is
keep_automatic, explicit entry uses until_resumed without changing the
saved default. The result is the full schedule response.
Errors are not_loaded, temperature_migration_required,
operation_in_progress, or manual_adjustment_not_allowed. The last includes
an unmanaged, unavailable, disabled, already-manual, Profile-paused, otherwise
paused, or non-automatic zone.
await hass.connection.sendMessagePromise({
type: "velair/resume_automatic_control",
entity_id: "climate.living_room",
});The only parameter is the required managed entity_id. Velair removes only
its Manual-adjustment pause reason, resolves the current Profile, Mode,
schedule, Boost, and remaining pause authority, and applies that intent when
allowed. It does not remove independent pause reasons. The result is the full
schedule response; calling it when the zone is already automatic is harmless.
Errors are not_loaded, temperature_migration_required,
operation_in_progress, or invalid_entity. Malformed entity IDs fail
WebSocket schema validation.
await hass.connection.sendMessagePromise({
type: "velair/update_zone_preconditioning",
entity_id: "climate.living_room",
preconditioning: {
enabled: true,
max_lead_minutes: 1440,
minimum_delta_temperature: 0.3,
min_start_minutes: 10,
fallback_minutes_per_degree: 25,
room_temperature_entity_id: "sensor.living_room_temperature",
room_sensor_assist_enabled: true,
room_sensor_assist_deadband: 0.3,
room_sensor_assist_max_delta: 2.0
}
});When preconditioning moves the apply time earlier than the visible schedule block time, serialized events keep when as the apply time and include target_when as the comfort target time. These events also include preconditioning_diagnostics, a runtime-only calculation breakdown with selected sample counts, complete estimate, partial floor, combined estimate, rounded estimate, final lead, model source, and limit flags. The frontend uses this object for optional calculation details instead of recalculating the prediction.
Preconditioning is adaptive. The scheduler predicts a lead for each concrete future event, using an initial model while learning and switching to similar local history after enough complete samples exist.
Outdoor temperature context is optional and local. In the Preconditioning tab, outdoor_temperature_entity_id is selected through a sensor dropdown that lists local sensor.* temperature entities. Velair reads the selected sensor's numeric state, stores it with learning samples, and uses it only to compare similar preconditioning samples once enough history exists. It does not call external weather services and does not apply fixed weather-based adjustments to the initial model.
Room temperature sensor support is optional and local. In the Room Assist tab, room_temperature_entity_id is selected through a sensor dropdown that lists local sensor.* temperature entities. Selecting a sensor stores the configuration, but Velair uses it as the effective room temperature only when room_sensor_assist_enabled is true. In that mode Velair can temporarily adjust a scalar target or move a complete native range while the real scheduled target remains unchanged. For fixed scalar heat and cool, a non-zero room_sensor_assist_deadband defines a runtime hysteresis cycle between the scheduled target minus and plus that value; room_sensor_assist_max_delta caps correction relative to the active edge. A zero deadband preserves legacy signed correction. Scalar automatic modes retain their neutral deadband, while native ranges retain their stable holding margin. Scalar non-driving targets are kept on the safe side of the scheduled target, and a valid native-range holding band remains stable until the external room leaves it or its active target changes. room_sensor_assist_debounce_seconds controls how many seconds Velair waits after relevant state changes before recalculating the assisted target. Room Sensor Assist can run on normal scheduled blocks and can also provide the temperature source for Adaptive Preconditioning while it is enabled.
room_sensor_assist in the schedule response is runtime-only status. It is derived from Home Assistant state and scheduler state when the response is built; it is not persisted as history.
When the thermostat's physical minimum or maximum changes the target that Room
Assist actually applied, limited_by is minimum or maximum and
limit_temperature identifies that boundary. Scalar status then includes
requested_temperature; native ranges instead include
requested_target_temp_low and requested_target_temp_high. These optional
fields are null when the last applied runtime target was not limited.
For scalar targets, scheduled_target_guard is cooling_floor or
heating_ceiling when the scheduled target prevented a non-driving calculation
from crossing onto the demanding side. calculated_temperature contains the
step-aligned candidate before that protection, while applied_temperature
remains the target actually sent. Both fields are optional and runtime-only;
clients must treat their absence as an unguarded or older compatible payload.
For scalar targets, status can also include pre_step_temperature together
with target_temp_step when alignment to the climate's published step changed
the setpoint that was actually committed. pre_step_temperature is the
candidate after scheduled-target protection and min/max clamping, immediately
before alignment. The pair is absent for exact-step results, native ranges,
results changed by a final physical-limit clamp, and older compatible payloads.
Diagnostics copies these live status fields, but they are not persisted or
included in automation events or Home Assistant sensor attributes.
Fixed scalar hysteresis status can additionally include
hysteresis_phase (towards_lower or towards_upper), hysteresis_target,
deadband_low, and deadband_high. These fields are optional, runtime-only,
and absent for the zero-deadband, scalar automatic, and native-range paths.
The phase is not persisted and resets on an active block, target, or HVAC-mode
change, when assistance is cleared, and when Velair or Home Assistant reloads.
See Adaptive preconditioning for the full learning lifecycle, input/output examples, prediction rules, storage behavior, and known limitations. See Room Assist for the room sensor assistance lifecycle, target calculation, runtime status, restoration behavior, and events.
await hass.connection.sendMessagePromise({
type: "velair/update_zone_comfort",
entity_id: "climate.living_room",
comfort: {
enabled: true,
temperature_entity_id: "sensor.living_room_temperature",
humidity_enabled: true,
humidity_entity_id: "sensor.living_room_humidity",
co2_entity_id: "sensor.living_room_co2",
temperature_min: 20,
temperature_max: 24,
humidity_min: 40,
humidity_max: 60,
co2_attention: 1000,
co2_poor: 1500,
stale_after_minutes: 120
}
});Comfort settings are per managed climate. The scheduler only listens to comfort-related entities for climates where comfort.enabled is true. See Environmental Comfort internals for source selection, assessment calculation, runtime listener behavior, and event payloads.
Create or replace a complete profile definition. Omitting key creates a new
stable key; including it updates that profile. Profile zones are sparse:
omitted zones keep their default schedule.
await hass.connection.sendMessagePromise({
type: "velair/set_profile",
profile: {
name: "Away",
icon: "mdi:home-export-outline",
color: "#546e7a",
description: "Lower demand while nobody is home",
zones: {
"climate.living_room": {
behavior: "pause",
action: "turn_off"
}
}
}
});The response is the full schedule response plus profile_id, containing the
created or updated key. Delete a profile with:
await hass.connection.sendMessagePromise({
type: "velair/delete_profile",
key: "away"
});Deleting an active Profile removes only that ID from the active set; its zones
return to Default and other active Profiles remain. Activate one Profile as a
Manual singleton, or empty the active set with null for compatibility, using:
await hass.connection.sendMessagePromise({
type: "velair/activate_profile",
profile_id: "away"
});Activation replaces the complete active set with that Profile. Zones present in
the previous set but not covered by the selected Profile resolve immediately to
their Default schedules. It applies the current effective schedule, cancels
Boost on affected zones, and preserves global or per-zone manual pauses. Direct
activation clears active_mode_id, so the native selector reports
Manual. New automations should use velair.deactivate_profile explicitly to
return to default schedules.
Create or update one Mode with:
await hass.connection.sendMessagePromise({
type: "velair/set_mode",
mode: {
name: "Away",
profile_ids: ["away"]
}
});Omit key when creating a mode; include the stable returned key when updating
one. Names are trimmed, limited to 255 characters, case-insensitively unique,
cannot contain control characters, and cannot use the built-in names Default
or Manual, translated built-in labels such as Predeterminado, nor the Home
Assistant states unknown and unavailable.
Every Mode must provide one or more unique existing IDs in profile_ids.
Selected Profiles must not explicitly configure the same zone; conflicting
compositions are rejected. Delete a Mode with:
await hass.connection.sendMessagePromise({
type: "velair/delete_mode",
key: "away-mode"
});The schedule response contains modes and active_mode_id.
Automations select a configured value through Home Assistant's standard
select.select_option action on select.velair_mode.
The Velair panel changes the active Mode through a structured WebSocket command so it does not depend on the user-editable Home Assistant entity ID:
await hass.connection.sendMessagePromise({
type: "velair/select_mode",
selection: { kind: "custom", key: "away-mode" }
});Custom selections use the stable mode key. The built-in selections are
{ kind: "default" } and { kind: "manual" }. Panel custom selections report
panel as their profile-change source; changes made through the native select
entity continue to report select.
await hass.connection.sendMessagePromise({
type: "velair/reset_zone_preconditioning_settings",
entity_id: "climate.living_room"
});Restores default tuning parameters for one managed climate. The current enabled state, schedules, and all heat and cool learning samples are preserved.
await hass.connection.sendMessagePromise({
type: "velair/reset_zone_preconditioning_learning",
entity_id: "climate.living_room",
direction: "heat"
});Deletes local adaptive preconditioning observations for one managed climate direction. Valid directions are heat and cool. Schedule blocks, preconditioning settings, and the other direction's observations are kept.
await hass.connection.sendMessagePromise({
type: "velair/export_data",
sections: ["zones", "templates", "settings", "preconditioning_learning", "profiles", "modes"],
});Returns a versioned portable JSON payload:
{
"format": "velair_portable_data",
"model_version": 8,
"temperature_unit": "°C",
"exported_at": "2026-05-25T00:00:00+00:00",
"sections": {}
}await hass.connection.sendMessagePromise({
type: "velair/import_data",
payload,
sections: ["templates"]
});Selected sections overwrite existing data. Profile and Mode definitions are portable, but the active Profile set and selected Mode are never exported or selected by import. Replacing definitions retains active IDs that still exist and returns to default schedules only when the active set becomes empty.
Portable model v5 payloads add the optional modes section. V4 payloads
remain valid. Portable payloads declare temperature_unit; models created before
unit metadata existed may omit it; the backend treats those values as Celsius.
Portable model v8 adds the independent room_sensor_assist_deadband. When a v7
or older zone omits it, import migrates the legacy
minimum_delta_temperature value before any unit conversion.
If the source differs from Velair's current Home Assistant unit, selected thermal
data is converted before normalization. Managed climates with known limits and
target_temp_step are aligned to that exact grid. Standalone template values use
safe fallback precision when no common exact device step can be derived. Data for
unmatched climate IDs is not applied or used to transform existing local zones.
Export is available while scheduling is blocked by a pending temperature-unit resolution and remains a read-only operation during reset, migration, or import.
The preconditioning_learning section is incremental by climate entity ID: matching managed climates receive the normalized imported history, unknown climate IDs are ignored, and existing history for local climates absent from the file is preserved.
await hass.connection.sendMessagePromise({
type: "velair/reset_data",
confirmation: "reset"
});This deletes all stored Velair data, including schedules, templates, panel preferences, active boosts and pauses, per-zone external-change policies and persisted Manual-adjustment state, Comfort and Room Assist settings, Adaptive Preconditioning settings and learning, and startup behavior. It then recreates unit-aware defaults for the currently managed climates. The separate Diagnostics category policy and runtime log are not schedule data reset by this command. The frontend must ask the user for confirmation before calling this command.
not_loaded: the integration is not loaded.invalid_schedule: a schedule is invalid or targets an unmanaged climate.invalid_template: a template is invalid or unknown.invalid_settings: settings are invalid.invalid_external_change_policy: an external-change policy or target is invalid.manual_adjustment_not_allowed: the requested zone cannot enter Manual adjustment in its current state.invalid_entity: the requested entity is not managed by Velair.invalid_preconditioning: preconditioning settings are invalid or target an unmanaged climate.invalid_comfort: Comfort settings are invalid or target an unmanaged climate.invalid_import: the import file is invalid or incompatible.invalid_temperature_migration: the requested source, revision, or migration state is invalid.operation_in_progress: a reset, migration, or import currently owns the data write guard.operation_recovery_required: data was persisted, but runtime cleanup or option updates failed. The scheduler remains stopped until the integration is reloaded or Home Assistant restarts.temperature_migration_required: thermal writes remain stopped until the stored unit is resolved.temperature_migration_failed: the migration could not be persisted; its write guard was released and scheduling remains governed by the still-stored unit metadata.
Climate actions use blocking Home Assistant calls so explicit invocation failures are observable. Scheduler-owned intentions may complete later through bounded runtime recovery; delayed attempts always resolve current backend state instead of replaying the original service payload. Manual temperature actions remain one-shot and report an unavailable or rejected call to their caller.