diff --git a/public/locales/en/modules/smart-home/entity-state.json b/public/locales/en/modules/smart-home/entity-state.json index f7eacbbb2..99048e629 100644 --- a/public/locales/en/modules/smart-home/entity-state.json +++ b/public/locales/en/modules/smart-home/entity-state.json @@ -22,8 +22,13 @@ }, "displayFriendlyName": { "label": "Display friendly name", - "info": "Display friendly name from Home Assistant instead instead of display name" + "info": "Display friendly name from Home Assistant instead instead of display name." + }, + "genericToggle": { + "label": "Entity toggle", + "info": "Perform a generic Home Assistant toggle action on entity when clicked." } } } -} \ No newline at end of file +} + diff --git a/src/server/api/routers/smart-home/entity-state.ts b/src/server/api/routers/smart-home/entity-state.ts index fdc204f16..e9d974f3a 100644 --- a/src/server/api/routers/smart-home/entity-state.ts +++ b/src/server/api/routers/smart-home/entity-state.ts @@ -88,6 +88,36 @@ export const smartHomeEntityStateRouter = createTRPCRouter({ } } + return false; + }), + triggerToggle: protectedProcedure + .input(z.object({ + widgetId: z.string(), + configName: z.string() + })).mutation(async ({ input }) => { + const config = getConfig(input.configName); + const widget = config.widgets.find(widget => widget.id === input.widgetId) as ISmartHomeEntityStateWidget | null; + + if (!widget) { + Consola.error(`Referenced widget ${input.widgetId} does not exist on backend.`); + throw new TRPCError({ + code: 'CONFLICT', + message: 'Referenced widget does not exist on backend', + }); + } + + const instances = config.apps.filter((app) => app.integration?.type == 'homeAssistant'); + + for (const instance of instances) { + const url = new URL(instance.url); + const client = HomeAssistantSingleton.getOrSet(url, findAppProperty(instance, 'apiKey')); + const state = await client.triggerToggle(widget.properties.entityId); + + if (state) { + return true; + } + } + return false; }), }); diff --git a/src/tools/server/sdk/homeassistant/HomeAssistant.ts b/src/tools/server/sdk/homeassistant/HomeAssistant.ts index 1a317772c..2f443edd6 100644 --- a/src/tools/server/sdk/homeassistant/HomeAssistant.ts +++ b/src/tools/server/sdk/homeassistant/HomeAssistant.ts @@ -56,4 +56,28 @@ export class HomeAssistant { return false; } } + + /** + * Triggers a toggle action for a specific entity. + * + * @param entityId - The ID of the entity to toggle. + * @returns A boolean indicating whether the toggle action was successful. + */ + async triggerToggle(entityId: string) { + try { + const response = await fetch(appendPath(this.basePath, `/services/homeassistant/toggle`), { + headers: { + 'Authorization': `Bearer ${this.token}`, + }, + body: JSON.stringify({ + 'entity_id': entityId, + }), + method: 'POST' + }); + return response.ok; + } catch (err) { + Consola.error(`Failed to fetch from '${this.basePath}': ${err}`); + return false; + } + } } diff --git a/src/widgets/smart-home/entity-state/entity-state.widget.tsx b/src/widgets/smart-home/entity-state/entity-state.widget.tsx index e448fd235..f2d3c8993 100644 --- a/src/widgets/smart-home/entity-state/entity-state.widget.tsx +++ b/src/widgets/smart-home/entity-state/entity-state.widget.tsx @@ -21,6 +21,11 @@ const definition = defineWidget({ defaultValue: false, info: true, }, + genericToggle: { + type: 'switch', + defaultValue: false, + info: true, + }, automationId: { type: 'text', info: true, @@ -82,15 +87,26 @@ function EntityStateTile({ widget }: SmartHomeEntityStateWidgetProps) { }, }); - const handleClick = async () => { - if (!widget.properties.automationId) { - return; - } + const { mutateAsync: mutateTriggerToggleSync } = api.smartHomeEntityState.triggerToggle.useMutation({ + onSuccess: () => { + void utils.smartHomeEntityState.invalidate(); + }, + }); - await mutateTriggerAutomationAsync({ - configName: configName as string, - widgetId: widget.id, - }); + const handleClick = async () => { + if (widget.properties.genericToggle) { + await mutateTriggerToggleSync({ + configName: configName as string, + widgetId: widget.id, + }); + } + + if (widget.properties.automationId) { + await mutateTriggerAutomationAsync({ + configName: configName as string, + widgetId: widget.id, + }); + } }; let dataComponent = null;