Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add custom state template and custom service call to button #337

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions data/dashboard.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,42 @@ views:
- name: Test
id: 2171067078835
items:
- type: button
id: 1063494948270
entity_id: switch.switch_buero_pc_sonos_strip
name: O's PC, Sonos, Strip
extras:
state_template: >
{% if(is_state('switch.switch_buero_pc_sonos_strip', 'on'))
%}
{{ "An • " + states('sensor.switch_buero_pc_sonos_strip_power', with_unit=True) }}
{% elif(is_state('switch.switch_buero_pc_sonos_strip',
'off')) %}
Aus
{% else %}
{{ states('switch.switch_buero_pc_sonos_strip') }}
{% endif %}
icon: mdi:power-socket-de
- type: button
id: 1113219359563
entity_id: switch.switch_buero_monitore_co
name: J's PC, Monitore
more_info: false
icon: mdi:power-socket-de
extras:
state_template: >
{% if(is_state('switch.switch_buero_monitore_co', 'on')) %}
{{ "An • " + states('sensor.switch_buero_monitore_co_power', with_unit=True) }}
{% elif(is_state('switch.switch_buero_monitore_co', 'off'))
%}
Aus
{% else %}
{{ states('switch.switch_buero_monitore_co') }}
{% endif %}
service_domain: light
service_service: turn_on
service_data:
entity_id: light.hue_buero
- type: button
entity_id: light.studio_dator
id: 5008685947164
Expand Down
59 changes: 56 additions & 3 deletions src/lib/Components/StateLogic.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
<script lang="ts">
import { editMode, lang, selectedLanguage, states } from '$lib/Stores';
import {
config,
connection,
editMode,
lang,
selectedLanguage,
states,
templates
} from '$lib/Stores';
import { getDomain, isTimestamp, relativeTime } from '$lib/Utils';
import type { HassEntity } from 'home-assistant-js-websocket';
import { marked } from 'marked';
import { onDestroy } from 'svelte';

export let selected: any;
export let contentWidth: number | undefined = undefined;
Expand All @@ -18,14 +28,57 @@
$: percentage = attributes?.percentage;
$: media_title = attributes?.media_title;

let unsubscribe: () => void;
let state_template_error = false;

$: statePrecision =
selected?.precision !== undefined && !isNaN(parseFloat(entity?.state))
? parseFloat(entity?.state).toFixed(selected?.precision)
: undefined;

$: template = selected?.extras?.state_template;

$: if ($config?.state === 'RUNNING' && template) {
renderTemplate(template, selected?.id + '_state');
}

/**
* Renders template by id to `$templates`
*/
async function renderTemplate(data: string, id: string) {
if (!$connection) return;

const handleResponse = (response: { result?: string }) => {
if (response?.result && id) {
$templates[id] = marked.parseInline(response.result) as string;
state_template_error = false;
}
};

const handleError = (err: any) => {
if (err?.code === 'template_error' && id) {
console.warn('render_template', err);
$templates[id] = err.message;
state_template_error = true;
}
};

try {
unsubscribe = await $connection.subscribeMessage(handleResponse, {
type: 'render_template',
template: data
});
} catch (error) {
handleError(error);
}
}
</script>

<!-- Light -->
{#if selected?.attribute}
<!-- Template -->
{#if selected?.id && $templates?.[selected?.id + '_state']}
<span class:state_template_error>{@html $templates?.[selected?.id + '_state']}</span>
<!-- Light -->
{:else if selected?.attribute}
{entity?.attributes[selected?.attribute]}
{:else if state === 'on' && brightness}
{@const percentage = brightness / 255}
Expand Down
132 changes: 79 additions & 53 deletions src/lib/Main/Button.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,17 @@
export let sectionName: string | undefined = undefined;

$: entity_id = demo || sel?.entity_id;
$: service_entity_id = sel?.extras?.service_data?.entity_id || sel?.entity_id;
$: icon = sel?.icon;
$: color = sel?.color;
$: marquee = sel?.marquee;
$: more_info = sel?.more_info;
$: service_domain = sel?.extras?.service_domain;
$: service_service = sel?.extras?.service_service;
$: service_data = sel?.extras?.service_data;

let entity: HassEntity;
let service_entity: HassEntity;
let contentWidth: number;
let container: HTMLDivElement;
let loading: boolean;
Expand All @@ -38,16 +43,18 @@
let delayLoading: ReturnType<typeof setTimeout> | null;

/**
* Observes changes in the `last_updated` property of an entity.
* Observes changes in the `last_updated` property of an entity or the entity id used in the service call, if defined.
* When the `last_updated` property changes:
*
* - Updates `entity` with the new state from `$states`
* - Resets the `loading` state to `false`
* - Clears any pending loading or reset timeouts
*/
$: if (entity_id && $states?.[entity_id]?.last_updated !== entity?.last_updated) {
entity = $states?.[entity_id];

$: if (
service_entity_id &&
$states?.[service_entity_id]?.last_updated !== service_entity?.last_updated
) {
service_entity = $states?.[service_entity_id];
loading = false;

if (delayLoading) {
Expand All @@ -61,6 +68,11 @@
}
}

// Observes changes in the `last_updated` property of an entity (for ex. attributes).
$: if (entity_id && $states?.[entity_id]?.last_updated !== entity?.last_updated) {
entity = $states?.[entity_id];
}

$: attributes = entity?.attributes;

$: iconColor = color
Expand All @@ -81,55 +93,69 @@
* using the correct service call...
*/
function toggle() {
const domain = getDomain(entity_id);
const state = entity?.state;
if (!domain || !state) return;

const services: Record<string, string> = {
automation: 'toggle',
button: 'press',
cover: 'toggle',
fan: 'toggle',
humidifier: 'toggle',
input_boolean: 'toggle',
input_button: 'press',
light: 'toggle',
lock: state === 'locked' ? 'unlock' : 'lock',
media_player: 'toggle',
scene: 'turn_on',
script: 'toggle',
siren: 'toggle',
switch: 'toggle',
timer: state === 'active' ? 'pause' : 'start',
vacuum: 'toggle'
};

switch (domain) {
// case 'person':
// console.debug('ping phone?');
// break;

case 'remote':
callService($connection, 'homeassistant', 'toggle', { entity_id });
break;

default:
if (domain in services) {
callService($connection, domain, services[domain], { entity_id });

// loader
delayLoading = setTimeout(() => {
loading = true;
}, $motion);

// loader 20s fallback
resetLoading = setTimeout(() => {
loading = false;
}, 20_000);
} else {
// not listed above just open modal
handleClickEvent();
}
if (service_data && service_domain && service_service) {
callService($connection, service_domain, service_service, service_data);

// loader
delayLoading = setTimeout(() => {
loading = true;
}, $motion);

// loader 20s fallback
resetLoading = setTimeout(() => {
loading = false;
}, 20_000);
} else {
const domain = getDomain(entity_id);
const state = entity?.state;
if (!domain || !state) return;

const services: Record<string, string> = {
automation: 'toggle',
button: 'press',
cover: 'toggle',
fan: 'toggle',
humidifier: 'toggle',
input_boolean: 'toggle',
input_button: 'press',
light: 'toggle',
lock: state === 'locked' ? 'unlock' : 'lock',
media_player: 'toggle',
scene: 'turn_on',
script: 'toggle',
siren: 'toggle',
switch: 'toggle',
timer: state === 'active' ? 'pause' : 'start',
vacuum: 'toggle'
};

switch (domain) {
// case 'person':
// console.debug('ping phone?');
// break;

case 'remote':
callService($connection, 'homeassistant', 'toggle', { entity_id });
break;

default:
if (domain in services) {
callService($connection, domain, services[domain], { entity_id });

// loader
delayLoading = setTimeout(() => {
loading = true;
}, $motion);

// loader 20s fallback
resetLoading = setTimeout(() => {
loading = false;
}, 20_000);
} else {
// not listed above just open modal
handleClickEvent();
}
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib/Stores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export const highlightView = writable<boolean>(false);
// sidebar
export const timers = writable<{ [key: string]: { pausedState: string } }>({});
export const barErrors = writable<{ [key: string]: string }>({});
export const templates = writable<{ [key: number]: string }>({});
export const templates = writable<{ [key: string]: string }>({});
export const demo = writable<{ [key: string]: string | undefined }>({
graph: undefined,
sensor: undefined,
Expand Down
6 changes: 6 additions & 0 deletions src/lib/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ export interface ButtonItem {
precision: number;
more_info?: boolean;
attribute?: string;
extras?: {
state_template?: string;
service_domain?: string;
service_service?: string;
service_data?: object;
};
}

export type SidebarItem = BarItem &
Expand Down