diff --git a/gallery/src/pages/more-info/climate.ts b/gallery/src/pages/more-info/climate.ts index 95cc65321e..daa3554c50 100644 --- a/gallery/src/pages/more-info/climate.ts +++ b/gallery/src/pages/more-info/climate.ts @@ -43,6 +43,28 @@ const ENTITIES = [ target_temp_low: 20, target_temp_high: 25, }), + getEntity("climate", "advanced", "auto", { + friendly_name: "Advanced hvac", + supported_features: + // eslint-disable-next-line no-bitwise + ClimateEntityFeature.TARGET_TEMPERATURE_RANGE | + ClimateEntityFeature.TARGET_HUMIDITY | + ClimateEntityFeature.PRESET_MODE, + hvac_modes: ["auto", "off"], + hvac_mode: "auto", + preset_modes: ["eco", "comfort", "boost"], + preset_mode: "eco", + current_temperature: 18, + min_temp: 10, + max_temp: 30, + target_temp_step: 1, + target_temp_low: 20, + target_temp_high: 25, + current_humidity: 40, + min_humidity: 0, + max_humidity: 100, + humidity: 50, + }), getEntity("climate", "unavailable", "unavailable", { friendly_name: "Unavailable heater", hvac_modes: ["heat", "off"], diff --git a/gallery/src/pages/more-info/water-heater.markdown b/gallery/src/pages/more-info/water-heater.markdown new file mode 100644 index 0000000000..52dd5b3be1 --- /dev/null +++ b/gallery/src/pages/more-info/water-heater.markdown @@ -0,0 +1,3 @@ +--- +title: Water Heater +--- diff --git a/gallery/src/pages/more-info/water-heater.ts b/gallery/src/pages/more-info/water-heater.ts new file mode 100644 index 0000000000..8210dcac55 --- /dev/null +++ b/gallery/src/pages/more-info/water-heater.ts @@ -0,0 +1,70 @@ +import { html, LitElement, PropertyValues, TemplateResult } from "lit"; +import { customElement, property, query } from "lit/decorators"; +import "../../../../src/components/ha-card"; +import { WaterHeaterEntityFeature } from "../../../../src/data/water_heater"; +import "../../../../src/dialogs/more-info/more-info-content"; +import { getEntity } from "../../../../src/fake_data/entity"; +import { + MockHomeAssistant, + provideHass, +} from "../../../../src/fake_data/provide_hass"; +import "../../components/demo-more-infos"; + +const ENTITIES = [ + getEntity("water_heater", "basic", "eco", { + friendly_name: "Basic heater", + operation_list: ["heat_pump", "eco", "performance", "off"], + operation_mode: "eco", + away_mode: "off", + target_temp_step: 1, + current_temperature: 55, + temperature: 60, + min_temp: 20, + max_temp: 70, + supported_features: + // eslint-disable-next-line no-bitwise + WaterHeaterEntityFeature.TARGET_TEMPERATURE | + WaterHeaterEntityFeature.OPERATION_MODE | + WaterHeaterEntityFeature.AWAY_MODE, + }), + getEntity("water_heater", "unavailable", "unavailable", { + friendly_name: "Unavailable heater", + operation_list: ["heat_pump", "eco", "performance", "off"], + operation_mode: "off", + min_temp: 20, + max_temp: 70, + supported_features: + // eslint-disable-next-line no-bitwise + WaterHeaterEntityFeature.TARGET_TEMPERATURE | + WaterHeaterEntityFeature.OPERATION_MODE, + }), +]; + +@customElement("demo-more-info-water-heater") +class DemoMoreInfoWaterHeater extends LitElement { + @property() public hass!: MockHomeAssistant; + + @query("demo-more-infos") private _demoRoot!: HTMLElement; + + protected render(): TemplateResult { + return html` + ent.entityId)} + > + `; + } + + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + const hass = provideHass(this._demoRoot); + hass.updateTranslations(null, "en"); + hass.addEntities(ENTITIES); + } +} + +declare global { + interface HTMLElementTagNameMap { + "demo-more-info-water-heater": DemoMoreInfoWaterHeater; + } +} diff --git a/src/dialogs/more-info/components/climate/ha-more-info-climate-temperature.ts b/src/dialogs/more-info/components/climate/ha-more-info-climate-temperature.ts index e9ddb81c11..1d7306f439 100644 --- a/src/dialogs/more-info/components/climate/ha-more-info-climate-temperature.ts +++ b/src/dialogs/more-info/components/climate/ha-more-info-climate-temperature.ts @@ -416,11 +416,12 @@ export class HaMoreInfoClimateTemperature extends LitElement { font-size: 20px; line-height: 24px; align-self: flex-start; - margin-left: -20px; width: 20px; margin-top: 4px; } - + .decimal + .unit { + margin-left: -20px; + } .dual { display: flex; flex-direction: row; diff --git a/src/dialogs/more-info/components/water_heater/ha-more-info-water_heater-temperature.ts b/src/dialogs/more-info/components/water_heater/ha-more-info-water_heater-temperature.ts index a23d89fc79..3d2319688c 100644 --- a/src/dialogs/more-info/components/water_heater/ha-more-info-water_heater-temperature.ts +++ b/src/dialogs/more-info/components/water_heater/ha-more-info-water_heater-temperature.ts @@ -243,10 +243,12 @@ export class HaMoreInfoWaterHeaterTemperature extends LitElement { font-size: 20px; line-height: 24px; align-self: flex-start; - margin-left: -20px; width: 20px; margin-top: 4px; } + .decimal + .unit { + margin-left: -20px; + } `, ]; } diff --git a/src/fake_data/entity.ts b/src/fake_data/entity.ts index de4b0ca5a5..f6e6912dfd 100644 --- a/src/fake_data/entity.ts +++ b/src/fake_data/entity.ts @@ -356,6 +356,26 @@ class ClimateEntity extends Entity { } } +class WaterHeaterEntity extends Entity { + public async handleService(domain, service, data) { + if (domain !== this.domain) { + return; + } + + if (service === "set_operation_mode") { + this.update(data.operation_mode, this.attributes); + } else if (["set_temperature"].includes(service)) { + const { entity_id, ...toSet } = data; + this.update(this.state, { + ...this.attributes, + ...toSet, + }); + } else { + super.handleService(domain, service, data); + } + } +} + class GroupEntity extends Entity { public async handleService(domain, service, data) { if (!["homeassistant", this.domain].includes(domain)) { @@ -386,6 +406,7 @@ const TYPES = { lock: LockEntity, media_player: MediaPlayerEntity, switch: ToggleEntity, + water_heater: WaterHeaterEntity, }; export const getEntity = ( diff --git a/src/fake_data/provide_hass.ts b/src/fake_data/provide_hass.ts index 3da61ed4fe..297f4d4f8c 100644 --- a/src/fake_data/provide_hass.ts +++ b/src/fake_data/provide_hass.ts @@ -1,15 +1,16 @@ -import { HassEntities } from "home-assistant-js-websocket"; +import { HassEntities, HassEntity } from "home-assistant-js-websocket"; import { applyThemesOnElement, invalidateThemeCache, } from "../common/dom/apply_themes_on_element"; import { fireEvent } from "../common/dom/fire_event"; +import { computeFormatFunctions } from "../common/translations/entity-state"; import { computeLocalize } from "../common/translations/localize"; import { DEFAULT_PANEL } from "../data/panel"; import { + DateFormat, FirstWeekday, NumberFormat, - DateFormat, TimeFormat, TimeZone, } from "../data/translation"; @@ -49,6 +50,13 @@ export interface MockHomeAssistant extends HomeAssistant { mockAPI(path: string | RegExp, callback: MockRestCallback); mockEvent(event); mockTheme(theme: Record | null); + formatEntityState(stateObj: HassEntity, state?: string): string; + formatEntityAttributeValue( + stateObj: HassEntity, + attribute: string, + value?: any + ): string; + formatEntityAttributeName(stateObj: HassEntity, attribute: string): string; } export const provideHass = ( @@ -73,6 +81,7 @@ export const provideHass = ( const lang = language || getLocalLanguage(); const translation = await getTranslation(fragment, lang); await addTranslations(translation.data, lang); + updateFormatFunctions(); } async function addTranslations( @@ -101,6 +110,24 @@ export const provideHass = ( }); } + async function updateFormatFunctions() { + const { + formatEntityState, + formatEntityAttributeName, + formatEntityAttributeValue, + } = await computeFormatFunctions( + hass().localize, + hass().locale, + hass().config, + hass().entities + ); + hass().updateHass({ + formatEntityState, + formatEntityAttributeName, + formatEntityAttributeValue, + }); + } + function addEntities(newEntities, replace = false) { const states = {}; ensureArray(newEntities).forEach((ent) => { @@ -115,6 +142,7 @@ export const provideHass = ( } else { updateStates(states); } + updateFormatFunctions(); } function mockAPI(path, callback) { @@ -318,6 +346,11 @@ export const provideHass = ( areas: {}, devices: {}, entities: {}, + formatEntityState: (stateObj, state) => + (state !== null ? state : stateObj.state) ?? "", + formatEntityAttributeName: (_stateObj, attribute) => attribute, + formatEntityAttributeValue: (stateObj, attribute, value) => + value !== null ? value : stateObj.attributes[attribute] ?? "", ...overrideData, };