diff --git a/extensions/homeassistant/package-lock.json b/extensions/homeassistant/package-lock.json
index 09fc70b3eba..140b47910c6 100644
--- a/extensions/homeassistant/package-lock.json
+++ b/extensions/homeassistant/package-lock.json
@@ -1,14 +1,14 @@
{
"name": "homeassistant",
- "version": "1.0.0",
+ "version": "1.1.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
- "version": "1.0.0",
+ "version": "1.1.0",
"license": "MIT",
"dependencies": {
- "@raycast/api": "^1.24.5",
+ "@raycast/api": "^1.25.0",
"node-fetch": "^2.6.1",
"url-join": "^4.0.1"
},
@@ -212,9 +212,9 @@
}
},
"node_modules/@raycast/api": {
- "version": "1.24.5",
- "resolved": "https://registry.npmjs.org/@raycast/api/-/api-1.24.5.tgz",
- "integrity": "sha512-dv7cjwH+fcG1inQi8qPMbzkrZLc1HPsiDKO5lYQKFmtFMCIsSivg5zmXm7tCHhDGEUSi4uZK+9H7Mo6Ov51wdA==",
+ "version": "1.25.0",
+ "resolved": "https://registry.npmjs.org/@raycast/api/-/api-1.25.0.tgz",
+ "integrity": "sha512-uRvJ1LEz0wlDSmw2p+mT63CpUabViyy26AU0M8PCegAv96wtzNfxeuxPU18QmObADOTjbLTv/HcZD8fSCUHjjg==",
"dependencies": {
"fast-json-patch": "3.1.0",
"json-rpc-2.0": "0.2.19",
@@ -2243,9 +2243,9 @@
}
},
"@raycast/api": {
- "version": "1.24.5",
- "resolved": "https://registry.npmjs.org/@raycast/api/-/api-1.24.5.tgz",
- "integrity": "sha512-dv7cjwH+fcG1inQi8qPMbzkrZLc1HPsiDKO5lYQKFmtFMCIsSivg5zmXm7tCHhDGEUSi4uZK+9H7Mo6Ov51wdA==",
+ "version": "1.25.0",
+ "resolved": "https://registry.npmjs.org/@raycast/api/-/api-1.25.0.tgz",
+ "integrity": "sha512-uRvJ1LEz0wlDSmw2p+mT63CpUabViyy26AU0M8PCegAv96wtzNfxeuxPU18QmObADOTjbLTv/HcZD8fSCUHjjg==",
"requires": {
"fast-json-patch": "3.1.0",
"json-rpc-2.0": "0.2.19",
diff --git a/extensions/homeassistant/package.json b/extensions/homeassistant/package.json
index 428f82c586e..8099072b3ad 100644
--- a/extensions/homeassistant/package.json
+++ b/extensions/homeassistant/package.json
@@ -1,7 +1,7 @@
{
"name": "homeassistant",
"title": "Home Assistant",
- "version": "1.0.0",
+ "version": "1.1.0",
"author": "tonka3000",
"license": "MIT",
"description": "Home Assistant remote control. Control your house with Raycast 🚀",
@@ -76,6 +76,13 @@
"subtitle" : "Home Assistant",
"description": "Get/Set states of Home Assistant climate entities",
"mode": "view"
+ },
+ {
+ "name": "attributes",
+ "title": "All Entity Attributes",
+ "subtitle" : "Home Assistant",
+ "description": "Query Home Assistant entity attributes",
+ "mode": "view"
}
],
"preferences": [
@@ -97,7 +104,7 @@
}
],
"dependencies": {
- "@raycast/api": "^1.24.5",
+ "@raycast/api": "^1.25.0",
"node-fetch": "^2.6.1",
"url-join": "^4.0.1"
},
diff --git a/extensions/homeassistant/src/attributes.tsx b/extensions/homeassistant/src/attributes.tsx
new file mode 100644
index 00000000000..dff9348c72f
--- /dev/null
+++ b/extensions/homeassistant/src/attributes.tsx
@@ -0,0 +1,5 @@
+import { StatesAttributesList } from "./components/attributes_all";
+
+export default function main() {
+ return ;
+}
diff --git a/extensions/homeassistant/src/automations.tsx b/extensions/homeassistant/src/automations.tsx
index 5bd0e6b0c87..1f862b92b1b 100644
--- a/extensions/homeassistant/src/automations.tsx
+++ b/extensions/homeassistant/src/automations.tsx
@@ -1,4 +1,4 @@
-import { StatesList } from "./components";
+import { StatesList } from "./components/states";
export default function main() {
return ;
diff --git a/extensions/homeassistant/src/binarysensors.tsx b/extensions/homeassistant/src/binarysensors.tsx
index b3cad75fcc7..22faf342eb4 100644
--- a/extensions/homeassistant/src/binarysensors.tsx
+++ b/extensions/homeassistant/src/binarysensors.tsx
@@ -1,4 +1,4 @@
-import { StatesList } from "./components";
+import { StatesList } from "./components/states";
export default function main() {
return ;
diff --git a/extensions/homeassistant/src/climate.tsx b/extensions/homeassistant/src/climate.tsx
index 8be50e669b6..031a6f24c6f 100644
--- a/extensions/homeassistant/src/climate.tsx
+++ b/extensions/homeassistant/src/climate.tsx
@@ -1,4 +1,4 @@
-import { StatesList } from "./components";
+import { StatesList } from "./components/states";
export default function main() {
return ;
diff --git a/extensions/homeassistant/src/components/attributes.tsx b/extensions/homeassistant/src/components/attributes.tsx
new file mode 100644
index 00000000000..3a9ca918e11
--- /dev/null
+++ b/extensions/homeassistant/src/components/attributes.tsx
@@ -0,0 +1,27 @@
+import { ActionPanel, CopyToClipboardAction, List } from "@raycast/api";
+import { State } from "../haapi";
+
+export function EntityAttributesList(props: { state: State }) {
+ const state = props.state;
+ const title = state.attributes.friendly_name
+ ? `${state.attributes.friendly_name} (${state.entity_id})`
+ : `${state.entity_id}`;
+ return (
+
+
+ {Object.entries(state.attributes).map(([k, v]) => (
+
+
+
+ }
+ />
+ ))}
+
+
+ );
+}
diff --git a/extensions/homeassistant/src/components/attributes_all.tsx b/extensions/homeassistant/src/components/attributes_all.tsx
new file mode 100644
index 00000000000..a956f7ff4eb
--- /dev/null
+++ b/extensions/homeassistant/src/components/attributes_all.tsx
@@ -0,0 +1,104 @@
+import { ActionPanel, CopyToClipboardAction, List, showToast, ToastStyle } from "@raycast/api";
+import { useState, useEffect } from "react";
+import { createHomeAssistantClient } from "../common";
+
+export const ha = createHomeAssistantClient();
+
+class Attribute {
+ public name = "";
+ public value: any;
+}
+
+export function StatesAttributesList(props: { domain: string }) {
+ const [searchText, setSearchText] = useState();
+ const { attributes, error, isLoading } = useSearch(searchText, props.domain);
+
+ if (error) {
+ showToast(ToastStyle.Failure, "Cannot search Home Assistant states", error);
+ }
+
+ if (!attributes) {
+ return
;
+ }
+
+ return (
+
+ {attributes?.map((attr) => (
+
+
+
+ }
+ />
+ ))}
+
+ );
+}
+
+export function useSearch(
+ query: string | undefined,
+ domain: string
+): {
+ attributes?: Attribute[];
+ error?: string;
+ isLoading: boolean;
+} {
+ const [attributes, setAttributes] = useState();
+ const [error, setError] = useState();
+ const [isLoading, setIsLoading] = useState(false);
+
+ let cancel = false;
+
+ useEffect(() => {
+ async function fetchData() {
+ if (query === null || cancel) {
+ return;
+ }
+
+ setIsLoading(true);
+ setError(undefined);
+
+ try {
+ const haStates = await ha.getStates({ domain: domain, query: "" });
+ let attributesData: Attribute[] = [];
+ haStates.forEach((e) =>
+ Object.entries(e.attributes).forEach(([k, v]) =>
+ attributesData.push({ name: `${e.entity_id}.${k}`, value: v })
+ )
+ );
+ if (query) {
+ const lquery = query.toLocaleLowerCase().trim();
+ attributesData = attributesData.filter((v) => v.name.toLocaleLowerCase().includes(lquery));
+ }
+ attributesData = attributesData.slice(0, 100);
+ if (!cancel) {
+ setAttributes(attributesData);
+ }
+ } catch (e: any) {
+ if (!cancel) {
+ setError(e.toString());
+ }
+ } finally {
+ if (!cancel) {
+ setIsLoading(false);
+ }
+ }
+ }
+
+ fetchData();
+
+ return () => {
+ cancel = true;
+ };
+ }, [query]);
+
+ return { attributes, error, isLoading };
+}
diff --git a/extensions/homeassistant/src/components.tsx b/extensions/homeassistant/src/components/states.tsx
similarity index 80%
rename from extensions/homeassistant/src/components.tsx
rename to extensions/homeassistant/src/components/states.tsx
index 31c858df557..e0be1c3bd12 100644
--- a/extensions/homeassistant/src/components.tsx
+++ b/extensions/homeassistant/src/components/states.tsx
@@ -1,24 +1,48 @@
import {
ActionPanel,
ActionPanelItem,
- ActionPanelSubmenu,
Color,
CopyToClipboardAction,
Icon,
+ ImageLike,
+ KeyboardShortcut,
List,
+ OpenAction,
popToRoot,
+ PushAction,
showToast,
ToastStyle,
} from "@raycast/api";
-import { HomeAssistant, State } from "./haapi";
+import { State } from "../haapi";
import { useState, useEffect } from "react";
-import { createHomeAssistantClient } from "./common";
+import { createHomeAssistantClient } from "../common";
+import { EntityAttributesList } from "./attributes";
export const ha = createHomeAssistantClient();
+export function ShowAttributesAction(props: { state: State }) {
+ if (props.state.attributes) {
+ return (
+ }
+ shortcut={{ modifiers: ["cmd", "shift"], key: "a" }}
+ icon={{ source: Icon.List, tintColor: Color.PrimaryText }}
+ />
+ );
+ } else {
+ return null;
+ }
+}
+
export function StatesList(props: { domain: string }) {
const [searchText, setSearchText] = useState();
- const { states, error, isLoading } = useSearch(searchText, props.domain);
+ const [updateTimestamp, setUpdateTimestamp] = useState(new Date());
+ const { states, error, isLoading } = useSearch(searchText, props.domain, updateTimestamp);
+
+ const refreshStates = () => {
+ setUpdateTimestamp(new Date());
+ };
if (error) {
showToast(ToastStyle.Failure, "Cannot search Home Assistant states", error);
@@ -36,44 +60,66 @@ export function StatesList(props: { domain: string }) {
title={state.attributes.friendly_name || state.entity_id}
subtitle={state.entity_id}
accessoryTitle={state.state}
- actions={}
+ actions={}
/>
))}
);
}
-export function StateActionPanel(props: { state: State }) {
+export function StateActionPanel(props: { state: State; refreshStates: () => void }) {
const state = props.state;
const domain = props.state.entity_id.split(".")[0];
const entityID = props.state.entity_id;
+ const refreshStates = props.refreshStates;
+
+ function StateActionItem(props: {
+ title: string;
+ onAction: () => Promise;
+ icon?: ImageLike | undefined;
+ shortcut?: KeyboardShortcut | undefined;
+ }) {
+ return (
+ {
+ await props.onAction();
+ refreshStates();
+ }}
+ icon={props.icon}
+ shortcut={props.shortcut}
+ />
+ );
+ }
+
switch (domain) {
case "cover": {
return (
- await ha.toggleCover(props.state.entity_id)}
icon={{ source: "toggle.png", tintColor: Color.PrimaryText }}
/>
- await ha.openCover(props.state.entity_id)}
icon={{ source: Icon.ChevronUp, tintColor: Color.PrimaryText }}
/>
- await ha.closeCover(props.state.entity_id)}
icon={{ source: Icon.ChevronDown, tintColor: Color.PrimaryText }}
/>
- await ha.stopCover(props.state.entity_id)}
icon={{ source: Icon.XmarkCircle, tintColor: Color.PrimaryText }}
/>
+
);
@@ -81,23 +127,24 @@ export function StateActionPanel(props: { state: State }) {
case "light": {
return (
- await ha.toggleLight(props.state.entity_id)}
icon={{ source: "toggle.png", tintColor: Color.PrimaryText }}
/>
- await ha.turnOnLight(props.state.entity_id)}
icon={{ source: "power.png", tintColor: Color.Green }}
/>
- await ha.turnOffLight(props.state.entity_id)}
icon={{ source: "power.png", tintColor: Color.Red }}
/>
+
);
@@ -105,57 +152,58 @@ export function StateActionPanel(props: { state: State }) {
case "media_player": {
return (
- await ha.playPauseMedia(entityID)}
icon={{ source: "play-pause.jpg", tintColor: Color.PrimaryText }}
/>
- await ha.playMedia(entityID)}
icon={{ source: "play.png", tintColor: Color.PrimaryText }}
/>
- await ha.pauseMedia(entityID)}
icon={{ source: "pause.png", tintColor: Color.PrimaryText }}
/>
- await ha.stopMedia(entityID)}
icon={{ source: Icon.XmarkCircle, tintColor: Color.PrimaryText }}
/>
- await ha.nextMedia(entityID)}
icon={{ source: "next.png", tintColor: Color.PrimaryText }}
/>
- await ha.previousMedia(entityID)}
icon={{ source: "previous.png", tintColor: Color.PrimaryText }}
/>
- await ha.volumeUpMedia(entityID)}
icon={{ source: Icon.SpeakerArrowUp, tintColor: Color.PrimaryText }}
/>
- await ha.volumeDownMedia(entityID)}
icon={{ source: Icon.SpeakerArrowDown, tintColor: Color.PrimaryText }}
/>
- await ha.muteMedia(entityID)}
icon={{ source: Icon.SpeakerSlash, tintColor: Color.PrimaryText }}
/>
+
@@ -163,7 +211,9 @@ export function StateActionPanel(props: { state: State }) {
}
case "climate": {
const changeTempAllowed =
- state.state === "heat" || state.state === "cool" || state.state == "auto" ? true : false;
+ state.state === "heat" || state.state === "cool" || state.state === "heat_cool" || state.state == "auto"
+ ? true
+ : false;
const currentTempValue: number | undefined = state.attributes.temperature || undefined;
const [currentTemp, setCurrentTemp] = useState(currentTempValue);
const upperTemp = currentTemp ? currentTemp + 0.5 : undefined;
@@ -186,7 +236,7 @@ export function StateActionPanel(props: { state: State }) {
icon={{ source: "thermometer.png", tintColor: Color.PrimaryText }}
>
{temps.map((t) => (
- {
@@ -204,7 +254,7 @@ export function StateActionPanel(props: { state: State }) {
icon={{ source: Icon.Gear, tintColor: Color.PrimaryText }}
>
{state.attributes.hvac_modes?.map((o: string) => (
- {
@@ -223,7 +273,7 @@ export function StateActionPanel(props: { state: State }) {
icon={{ source: Icon.List, tintColor: Color.PrimaryText }}
>
{preset_modes?.map((o: string) => (
- {
@@ -236,7 +286,7 @@ export function StateActionPanel(props: { state: State }) {
)}
{upperTemp && changeTempAllowed && (
- {
@@ -247,7 +297,7 @@ export function StateActionPanel(props: { state: State }) {
/>
)}
{lowerTemp && changeTempAllowed && (
- {
@@ -257,6 +307,7 @@ export function StateActionPanel(props: { state: State }) {
icon={{ source: "minus.png", tintColor: Color.PrimaryText }}
/>
)}
+
@@ -270,7 +321,8 @@ export function StateActionPanel(props: { state: State }) {
export function useSearch(
query: string | undefined,
- domain: string
+ domain: string,
+ updateTimestamp: Date
): {
states?: State[];
error?: string;
@@ -313,7 +365,7 @@ export function useSearch(
return () => {
cancel = true;
};
- }, [query]);
+ }, [query, updateTimestamp]);
return { states, error, isLoading };
}
diff --git a/extensions/homeassistant/src/covers.tsx b/extensions/homeassistant/src/covers.tsx
index e7ccbedb2ad..554b21c1208 100644
--- a/extensions/homeassistant/src/covers.tsx
+++ b/extensions/homeassistant/src/covers.tsx
@@ -1,4 +1,4 @@
-import { StatesList } from "./components";
+import { StatesList } from "./components/states";
export default function main() {
return ;
diff --git a/extensions/homeassistant/src/dashboard.tsx b/extensions/homeassistant/src/dashboard.tsx
index a3dbb0e0a35..7baade7e8d7 100644
--- a/extensions/homeassistant/src/dashboard.tsx
+++ b/extensions/homeassistant/src/dashboard.tsx
@@ -1,5 +1,5 @@
import { popToRoot, render, showHUD } from "@raycast/api";
-import { ha } from "./components";
+import { ha } from "./components/states";
import open from "open";
async function main() {
diff --git a/extensions/homeassistant/src/index.tsx b/extensions/homeassistant/src/index.tsx
index 7e3caa1d483..a8f9b60599f 100644
--- a/extensions/homeassistant/src/index.tsx
+++ b/extensions/homeassistant/src/index.tsx
@@ -1,4 +1,4 @@
-import { StatesList } from "./components";
+import { StatesList } from "./components/states";
export default function main() {
return ;
diff --git a/extensions/homeassistant/src/lights.tsx b/extensions/homeassistant/src/lights.tsx
index e3d12bc1095..85718a8180b 100644
--- a/extensions/homeassistant/src/lights.tsx
+++ b/extensions/homeassistant/src/lights.tsx
@@ -1,4 +1,4 @@
-import { StatesList } from "./components";
+import { StatesList } from "./components/states";
export default function main() {
return ;
diff --git a/extensions/homeassistant/src/mediaplayers.tsx b/extensions/homeassistant/src/mediaplayers.tsx
index d6c9fff1af4..b2cf7fb703e 100644
--- a/extensions/homeassistant/src/mediaplayers.tsx
+++ b/extensions/homeassistant/src/mediaplayers.tsx
@@ -1,4 +1,4 @@
-import { StatesList } from "./components";
+import { StatesList } from "./components/states";
export default function main() {
return ;
diff --git a/extensions/homeassistant/src/persons.tsx b/extensions/homeassistant/src/persons.tsx
index 3ed1d1c922f..f9eb3adb07b 100644
--- a/extensions/homeassistant/src/persons.tsx
+++ b/extensions/homeassistant/src/persons.tsx
@@ -1,4 +1,4 @@
-import { StatesList } from "./components";
+import { StatesList } from "./components/states";
export default function main() {
return ;
diff --git a/extensions/homeassistant/src/sensors.tsx b/extensions/homeassistant/src/sensors.tsx
index e8bf2d6bbca..28ed602477f 100644
--- a/extensions/homeassistant/src/sensors.tsx
+++ b/extensions/homeassistant/src/sensors.tsx
@@ -1,4 +1,4 @@
-import { StatesList } from "./components";
+import { StatesList } from "./components/states";
export default function main() {
return ;