diff --git a/apps/docs/docs/widgets/smart-home-entity-state/index.mdx b/apps/docs/docs/widgets/smart-home-entity-state/index.mdx
index d101d6376..0c991ccc6 100644
--- a/apps/docs/docs/widgets/smart-home-entity-state/index.mdx
+++ b/apps/docs/docs/widgets/smart-home-entity-state/index.mdx
@@ -43,6 +43,7 @@ import temperature from './img/temperature.png';
### Interactions
- Toggle entity state by clicking on the entity (only when clickable enabled).
+- After a successful toggle, the displayed state refreshes immediately.
### Adding the widget
@@ -64,4 +65,4 @@ import retrieveEntityId from './img/retrieve-entity-id.png';

-
\ No newline at end of file
+
diff --git a/apps/docs/docs/widgets/smart-home-execute-automation/index.mdx b/apps/docs/docs/widgets/smart-home-execute-automation/index.mdx
index 522c113ca..ce73df9d3 100644
--- a/apps/docs/docs/widgets/smart-home-execute-automation/index.mdx
+++ b/apps/docs/docs/widgets/smart-home-execute-automation/index.mdx
@@ -47,4 +47,7 @@ import withDisplayname from './img/with-displayname.png';
### Configuration
+
+Use the Home Assistant entity ID (for example, `automation.turn_on_lights`) in the **Automation ID** field, not the automation's internal ID.
+
diff --git a/packages/api/src/router/widgets/smart-home.ts b/packages/api/src/router/widgets/smart-home.ts
index c37468752..6efa16c9c 100644
--- a/packages/api/src/router/widgets/smart-home.ts
+++ b/packages/api/src/router/widgets/smart-home.ts
@@ -1,3 +1,4 @@
+import { TRPCError } from "@trpc/server";
import { z } from "zod/v4";
import { getIntegrationKindsByCategory } from "@homarr/definitions";
@@ -41,6 +42,13 @@ export const smartHomeRouter = createTRPCRouter({
const client = await createIntegrationAsync(integration);
const success = await client.triggerToggleAsync(input.entityId);
+ if (!success) {
+ throw new TRPCError({
+ code: "INTERNAL_SERVER_ERROR",
+ message: "Home Assistant failed to toggle the entity",
+ });
+ }
+
return success;
}),
executeAutomation: protectedProcedure
@@ -55,6 +63,13 @@ export const smartHomeRouter = createTRPCRouter({
.input(z.object({ automationId: z.string() }))
.mutation(async ({ ctx: { integration }, input }) => {
const client = await createIntegrationAsync(integration);
- await client.triggerAutomationAsync(input.automationId);
+ const success = await client.triggerAutomationAsync(input.automationId);
+
+ if (!success) {
+ throw new TRPCError({
+ code: "INTERNAL_SERVER_ERROR",
+ message: "Home Assistant failed to trigger the automation",
+ });
+ }
}),
});
diff --git a/packages/integrations/src/homeassistant/homeassistant-integration.ts b/packages/integrations/src/homeassistant/homeassistant-integration.ts
index 6da82df46..15c4ece7f 100644
--- a/packages/integrations/src/homeassistant/homeassistant-integration.ts
+++ b/packages/integrations/src/homeassistant/homeassistant-integration.ts
@@ -134,7 +134,10 @@ export class HomeAssistantIntegration extends Integration implements ISmartHomeI
*/
private async postAsync(path: `/api/${string}`, body: Record) {
return await fetchWithTrustedCertificatesAsync(this.url(path), {
- headers: this.getAuthHeaders(),
+ headers: {
+ ...this.getAuthHeaders(),
+ "Content-Type": "application/json",
+ },
body: JSON.stringify(body),
method: "POST",
});
diff --git a/packages/integrations/test/home-assistant-unit.spec.ts b/packages/integrations/test/home-assistant-unit.spec.ts
new file mode 100644
index 000000000..4887a95a4
--- /dev/null
+++ b/packages/integrations/test/home-assistant-unit.spec.ts
@@ -0,0 +1,48 @@
+// @vitest-environment node
+
+import { beforeEach, describe, expect, test, vi } from "vitest";
+
+import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
+
+import { HomeAssistantIntegration } from "../src";
+
+vi.mock("@homarr/core/infrastructure/http", () => ({
+ fetchWithTrustedCertificatesAsync: vi.fn(),
+}));
+
+const mockFetch = vi.mocked(fetchWithTrustedCertificatesAsync);
+const integration = new HomeAssistantIntegration({
+ id: "home-assistant",
+ name: "Home Assistant",
+ url: "http://home-assistant.local:8123",
+ externalUrl: null,
+ decryptedSecrets: [{ kind: "apiKey", value: "secret" }],
+});
+
+describe("HomeAssistantIntegration actions", () => {
+ beforeEach(() => {
+ mockFetch.mockReset();
+ mockFetch.mockResolvedValue({ ok: true } as never);
+ });
+
+ test.each([
+ ["toggle", () => integration.triggerToggleAsync("light.desk"), "/api/services/homeassistant/toggle", "light.desk"],
+ [
+ "automation",
+ () => integration.triggerAutomationAsync("automation.turn_on_lights"),
+ "/api/services/automation/trigger",
+ "automation.turn_on_lights",
+ ],
+ ])("sends the %s service call as JSON", async (_action, trigger, path, entityId) => {
+ await expect(trigger()).resolves.toBe(true);
+
+ expect(mockFetch).toHaveBeenCalledWith(new URL(`http://home-assistant.local:8123${path}`), {
+ headers: {
+ Authorization: "Bearer secret",
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ entity_id: entityId }),
+ method: "POST",
+ });
+ });
+});
diff --git a/packages/request-handler/src/smart-home-entity-state.ts b/packages/request-handler/src/smart-home-entity-state.ts
index 3c8851522..503690a37 100644
--- a/packages/request-handler/src/smart-home-entity-state.ts
+++ b/packages/request-handler/src/smart-home-entity-state.ts
@@ -18,4 +18,6 @@ export const smartHomeEntityStateRequestHandler = createIntegrationRequestHandle
return result.data.state;
},
+ // The widget already polls every 30 seconds and refetches after toggles.
+ cacheTtlMs: 0,
});