Skip to content

Commit

Permalink
feat(humidifier): add humidifier support (#346)
Browse files Browse the repository at this point in the history
* feat(humidifier): card

* feat(humidifier): improve card

* fix: view name

* fix: humidity live

* fix: MushroomBaseElement

* fix: clean unused dependencies

* fix: hacard

* fix: collapsible controls

* fix: remove buttons control

* fix: remove buttons control
  • Loading branch information
acesyde authored May 8, 2022
1 parent 2d95f27 commit 7f096bf
Show file tree
Hide file tree
Showing 18 changed files with 520 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .hass_dev/configuration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ fan:

lock:
- platform: demo

humidifier:
- platform: demo

person:
- name: Anne Therese
Expand Down
1 change: 1 addition & 0 deletions .hass_dev/lovelace-mushroom-showcase.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ views:
- !include views/media-player-view.yaml
- !include views/vacuum-view.yaml
- !include views/lock-view.yaml
- !include views/humidifier-view.yaml
51 changes: 51 additions & 0 deletions .hass_dev/views/humidifier-view.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
title: Humidifier
icon: mdi:air-humidifier
cards:
- type: grid
title: Simple
cards:
- type: custom:mushroom-humidifier-card
entity: humidifier.humidifier
- type: custom:mushroom-humidifier-card
entity: humidifier.humidifier
name: Custom name and icon
icon: mdi:robot-outline
columns: 2
square: false
- type: grid
title: Controls
cards:
- type: custom:mushroom-humidifier-card
entity: humidifier.humidifier
name: Humidity control
show_target_humidity_control: true
- type: custom:mushroom-humidifier-card
entity: humidifier.humidifier
name: Collapsible controls
collapsible_controls: true
show_target_humidity_control: true
columns: 2
square: false
- type: custom:mushroom-humidifier-card
entity: humidifier.humidifier
name: Multiple controls
show_target_humidity_control: true
- type: vertical-stack
title: Layout
cards:
- type: grid
columns: 2
square: false
cards:
- type: custom:mushroom-humidifier-card
entity: humidifier.humidifier
- type: grid
columns: 2
square: false
cards:
- type: custom:mushroom-humidifier-card
entity: humidifier.humidifier
layout: "vertical"
- type: custom:mushroom-humidifier-card
entity: humidifier.humidifier
layout: "horizontal"
26 changes: 26 additions & 0 deletions docs/cards/humidifier.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Humidifier card

![Humidifier light](../images/humidifier-light.png)
![Humidifier dark](../images/humidifier-dark.png)

## Description

A humidifier card allow you to control a humidifier entity.

## Configuration variables

All the options are available in the lovelace editor but you can use `yaml` if you want.

| Name | Type | Default | Description |
| :----------------------------- | :------ | :---------- | :---------------------------------------------------------------------------------- |
| `entity` | string | Required | Humidifier entity |
| `icon` | string | Optional | Custom icon |
| `name` | string | Optional | Custom name |
| `icon_color` | string | Optional | Custom icon color |
| `fill_container` | boolean | `false` | Fill container or not. Useful when card is in a grid, vertical or horizontal layout |
| `show_target_humidity_control` | boolean | Optional | Show target humidity control |
| `layout` | string | Optional | Layout of the card. Vertical, horizontal and default layout are supported |
| `hide_state` | boolean | `false` | Hide the entity state |
| `tap_action` | action | `more-info` | Home assistant action to perform on tap |
| `hold_action` | action | `more-info` | Home assistant action to perform on hold |
| `double_tap_action` | action | `more-info` | Home assistant action to perform on double_tap |
Binary file added docs/images/humidifier-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/humidifier-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/cards/humidifier-card/const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { PREFIX_NAME } from "../../const";

export const HUMIDIFIER_CARD_NAME = `${PREFIX_NAME}-humidifier-card`;
export const HUMIDIFIER_CARD_EDITOR_NAME = `${HUMIDIFIER_CARD_NAME}-editor`;
export const HUMIDIFIER_ENTITY_DOMAINS = ["humidifier"];
64 changes: 64 additions & 0 deletions src/cards/humidifier-card/controls/humidifier-humidity-control.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { HomeAssistant } from "custom-card-helpers";
import { html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators.js";
import { styleMap } from "lit/directives/style-map.js";
import { isActive, isAvailable } from "../../../ha/data/entity";
import { HumidifierEntity } from "../../../ha/data/humidifier";
import "../../../shared/slider";
import { computeRgbColor } from "../../../utils/colors";

@customElement("mushroom-humidifier-humidity-control")
export class HumidifierHumidityControl extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;

@property({ attribute: false }) public entity!: HumidifierEntity;

@property({ attribute: false }) public color!: string | undefined;

onChange(e: CustomEvent<{ value: number }>): void {
const value = e.detail.value;
this.hass.callService("humidifier", "set_humidity", {
entity_id: this.entity.entity_id,
humidity: value,
});
}

onCurrentChange(e: CustomEvent<{ value?: number }>): void {
const value = e.detail.value;
this.dispatchEvent(
new CustomEvent("current-change", {
detail: {
value,
},
})
);
}

protected render(): TemplateResult {
const max = this.entity.attributes.max_humidity || 100;
const min = this.entity.attributes.min_humidity || 0;

let sliderStyle = {
"--main-color": "rgb(var(--rgb-state-humidifier));",
"--bg-color": "rgba(var(--rgb-state-humidifier), 0.2);",
};

if (this.color) {
const rgbColor = computeRgbColor(this.color);
sliderStyle["--main-color"] = `rgb(${rgbColor})`;
sliderStyle["--bg-color"] = `rgba(${rgbColor}, 0.2)`;
}

return html`<mushroom-slider
.value=${this.entity.attributes.humidity}
.disabled=${!isAvailable(this.entity)}
.inactive=${!isActive(this.entity)}
.showActive=${true}
.min=${min}
.max=${max}
@change=${this.onChange}
style=${styleMap(sliderStyle)}
@current-change=${this.onCurrentChange}
/>`;
}
}
36 changes: 36 additions & 0 deletions src/cards/humidifier-card/humidifier-card-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ActionConfig, LovelaceCardConfig } from "custom-card-helpers";
import { assign, boolean, object, optional, string } from "superstruct";
import { actionConfigStruct } from "../../utils/action-struct";
import { baseLovelaceCardConfig } from "../../utils/editor-styles";
import { Layout, layoutStruct } from "../../utils/layout";

export interface HumidifierCardConfig extends LovelaceCardConfig {
entity?: string;
icon?: string;
icon_color?: string;
name?: string;
layout?: Layout;
fill_container?: boolean;
hide_state?: boolean;
show_target_humidity_control?: boolean;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
double_tap_action?: ActionConfig;
}

export const humidifierCardConfigStruct = assign(
baseLovelaceCardConfig,
object({
entity: optional(string()),
icon: optional(string()),
icon_color: optional(string()),
name: optional(string()),
layout: optional(layoutStruct),
fill_container: optional(boolean()),
hide_state: optional(boolean()),
show_target_humidity_control: optional(boolean()),
tap_action: optional(actionConfigStruct),
hold_action: optional(actionConfigStruct),
double_tap_action: optional(actionConfigStruct),
})
);
99 changes: 99 additions & 0 deletions src/cards/humidifier-card/humidifier-card-editor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { fireEvent, LovelaceCardEditor } from "custom-card-helpers";
import { CSSResultGroup, html, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators.js";
import memoizeOne from "memoize-one";
import { assert } from "superstruct";
import setupCustomlocalize from "../../localize";
import { MushroomBaseElement } from "../../utils/base-element";
import { GENERIC_LABELS } from "../../utils/form/generic-fields";
import { HaFormSchema } from "../../utils/form/ha-form";
import { stateIcon } from "../../utils/icons/state-icon";
import { loadHaComponents } from "../../utils/loader";
import { HUMIDIFIER_CARD_EDITOR_NAME, HUMIDIFIER_ENTITY_DOMAINS } from "./const";
import { HumidifierCardConfig, humidifierCardConfigStruct } from "./humidifier-card-config";

const HUMIDIFIER_FIELDS = ["show_target_humidity_control", "show_buttons_control"];

const computeSchema = memoizeOne((icon?: string): HaFormSchema[] => [
{ name: "entity", selector: { entity: { domain: HUMIDIFIER_ENTITY_DOMAINS } } },
{ name: "name", selector: { text: {} } },
{
type: "grid",
name: "",
schema: [
{ name: "icon", selector: { icon: { placeholder: icon } } },
{ name: "icon_color", selector: { "mush-color": {} } },
],
},
{
type: "grid",
name: "",
schema: [
{ name: "layout", selector: { "mush-layout": {} } },
{ name: "fill_container", selector: { boolean: {} } },
{ name: "hide_state", selector: { boolean: {} } },
],
},
{
type: "grid",
name: "",
schema: [
{ name: "show_target_humidity_control", selector: { boolean: {} } },
],
},
{ name: "tap_action", selector: { "mush-action": {} } },
{ name: "hold_action", selector: { "mush-action": {} } },
{ name: "double_tap_action", selector: { "mush-action": {} } },
]);

@customElement(HUMIDIFIER_CARD_EDITOR_NAME)
export class HumidifierCardEditor extends MushroomBaseElement implements LovelaceCardEditor {
@state() private _config?: HumidifierCardConfig;

connectedCallback() {
super.connectedCallback();
void loadHaComponents();
}

public setConfig(config: HumidifierCardConfig): void {
assert(config, humidifierCardConfigStruct);
this._config = config;
}

private _computeLabel = (schema: HaFormSchema) => {
const customLocalize = setupCustomlocalize(this.hass!);

if (GENERIC_LABELS.includes(schema.name)) {
return customLocalize(`editor.card.generic.${schema.name}`);
}
if (HUMIDIFIER_FIELDS.includes(schema.name)) {
return customLocalize(`editor.card.humidifier.${schema.name}`);
}
return this.hass!.localize(`ui.panel.lovelace.editor.card.generic.${schema.name}`);
};

protected render(): TemplateResult {
if (!this.hass || !this._config) {
return html``;
}

const entityState = this._config.entity ? this.hass.states[this._config.entity] : undefined;
const entityIcon = entityState ? stateIcon(entityState) : undefined;
const icon = this._config.icon || entityIcon;
const schema = computeSchema(icon);

return html`
<ha-form
.hass=${this.hass}
.data=${this._config}
.schema=${schema}
.computeLabel=${this._computeLabel}
@value-changed=${this._valueChanged}
></ha-form>
`;
}

private _valueChanged(ev: CustomEvent): void {
fireEvent(this, "config-changed", { config: ev.detail.value });
}
}
Loading

0 comments on commit 7f096bf

Please sign in to comment.