Skip to content

Commit

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

* feat(lock): lock card

* fix: missing hide state

* fix: review

* fix: review
  • Loading branch information
acesyde committed Apr 15, 2022
1 parent cceab7b commit 422e9a0
Show file tree
Hide file tree
Showing 15 changed files with 511 additions and 1 deletion.
1 change: 1 addition & 0 deletions .hass_dev/lovelace-mushroom-showcase.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ views:
- !include views/update-view.yaml
- !include views/media-player-view.yaml
- !include views/vacuum-view.yaml
- !include views/lock-view.yaml
47 changes: 47 additions & 0 deletions .hass_dev/views/lock-view.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
title: Lock
icon: mdi:lock
cards:
- type: grid
title: Simple
cards:
- type: custom:mushroom-lock-card
entity: lock.front_door
- type: custom:mushroom-lock-card
entity: lock.front_door
name: Custom name and icon
icon: mdi:robot-outline
columns: 2
square: false
- type: grid
title: Controls
cards:
- type: custom:mushroom-lock-card
entity: lock.front_door
name: Buttons control
- type: custom:mushroom-lock-card
entity: lock.front_door
name: Position control
columns: 2
square: false
- type: custom:mushroom-lock-card
entity: lock.front_door
name: Multiple controls
- type: vertical-stack
title: Layout
cards:
- type: grid
columns: 2
square: false
cards:
- type: custom:mushroom-lock-card
entity: lock.front_door
- type: grid
columns: 2
square: false
cards:
- type: custom:mushroom-lock-card
entity: lock.front_door
layout: "vertical"
- type: custom:mushroom-lock-card
entity: lock.front_door
layout: "horizontal"
24 changes: 24 additions & 0 deletions docs/cards/lock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Lock card

![Lock light](../images/lock-light.png)
![Lock dark](../images/lock-dark.png)

## Description

A lock card allow you to control a lock 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 | Lock entity |
| `icon` | string | Optional | Custom icon |
| `name` | string | Optional | Custom name |
| `icon_color` | string | Optional | Custom icon color |
| `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/lock-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/lock-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/cards/lock-card/const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { PREFIX_NAME } from "../../const";

export const LOCK_CARD_NAME = `${PREFIX_NAME}-lock-card`;
export const LOCK_CARD_EDITOR_NAME = `${LOCK_CARD_NAME}-editor`;
69 changes: 69 additions & 0 deletions src/cards/lock-card/controls/lock-buttons-control.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { computeRTL, HomeAssistant } from "custom-card-helpers";
import { html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators.js";
import { isAvailable } from "../../../ha/data/entity";
import { LockEntity } from "../../../ha/data/lock";
import { isActionPending, isLocked, isUnlocked } from "../utils";

interface LockButton {
icon: string;
serviceName?: string;
isVisible: (entity: LockEntity) => boolean;
isDisabled: (entity: LockEntity) => boolean;
}

export const LOCK_BUTTONS: LockButton[] = [
{
icon: "mdi:lock",
serviceName: "lock",
isVisible: (entity) => isUnlocked(entity),
isDisabled: () => false,
},
{
icon: "mdi:lock-open",
serviceName: "unlock",
isVisible: (entity) => isLocked(entity),
isDisabled: () => false,
},
{
icon: "mdi:lock-clock",
isVisible: (entity) => isActionPending(entity),
isDisabled: () => true,
},
];

@customElement("mushroom-lock-buttons-control")
export class LockButtonsControl extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;

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

@property() public fill: boolean = false;

private callService(e: CustomEvent) {
e.stopPropagation();
const entry = (e.target! as any).entry as LockButton;
this.hass.callService("lock", entry.serviceName!, {
entity_id: this.entity!.entity_id,
});
}

protected render(): TemplateResult {
const rtl = computeRTL(this.hass);

return html`
<mushroom-button-group .fill=${this.fill} .?rtl=${rtl}
>${LOCK_BUTTONS.filter((item) => item.isVisible(this.entity)).map(
(item) => html`
<mushroom-button
.icon=${item.icon}
.entry=${item}
.disabled=${!isAvailable(this.entity) || item.isDisabled(this.entity)}
@click=${this.callService}
></mushroom-button>
`
)}</mushroom-button-group
>
`;
}
}
32 changes: 32 additions & 0 deletions src/cards/lock-card/lock-card-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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 LockCardConfig extends LovelaceCardConfig {
entity?: string;
icon?: string;
name?: string;
icon_color?: string;
layout?: Layout;
hide_state?: boolean;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
double_tap_action?: ActionConfig;
}

export const lockCardConfigStruct = assign(
baseLovelaceCardConfig,
object({
entity: optional(string()),
name: optional(string()),
icon: optional(string()),
icon_color: optional(string()),
layout: optional(layoutStruct),
hide_state: optional(boolean()),
tap_action: optional(actionConfigStruct),
hold_action: optional(actionConfigStruct),
double_tap_action: optional(actionConfigStruct),
})
);
93 changes: 93 additions & 0 deletions src/cards/lock-card/lock-card-editor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { fireEvent, HomeAssistant, LovelaceCardEditor } from "custom-card-helpers";
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import memoizeOne from "memoize-one";
import { assert } from "superstruct";
import { LOCK_ENTITY_DOMAINS } from "../../ha/data/lock";
import setupCustomlocalize from "../../localize";
import { configElementStyle } from "../../utils/editor-styles";
import { GENERIC_FIELDS } from "../../utils/form/fields";
import { HaFormSchema } from "../../utils/form/ha-form";
import { stateIcon } from "../../utils/icons/state-icon";
import { loadHaComponents } from "../../utils/loader";
import { LOCK_CARD_EDITOR_NAME } from "./const";
import { LockCardConfig, lockCardConfigStruct } from "./lock-card-config";

const computeSchema = memoizeOne((icon?: string): HaFormSchema[] => [
{ name: "entity", selector: { entity: { domain : LOCK_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: "hide_state", selector: { boolean: {} } },
],
},
{ name: "tap_action", selector: { "mush-action": {} } },
{ name: "hold_action", selector: { "mush-action": {} } },
{ name: "double_tap_action", selector: { "mush-action": {} } },
]);

@customElement(LOCK_CARD_EDITOR_NAME)
export class LockCardEditor extends LitElement implements LovelaceCardEditor {
@property({ attribute: false }) public hass?: HomeAssistant;

@state() private _config?: LockCardConfig;

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

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

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

if (GENERIC_FIELDS.includes(schema.name)) {
return customLocalize(`editor.card.generic.${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._computeLabelCallback}
@value-changed=${this._valueChanged}
></ha-form>
`;
}

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

static get styles(): CSSResultGroup {
return configElementStyle;
}
}
Loading

0 comments on commit 422e9a0

Please sign in to comment.