Skip to content

Commit a0fa0de

Browse files
committed
Add support for showing specific sensors to HomeAssistant
# Conflicts: # docs/customservices.md
1 parent 92a79ff commit a0fa0de

File tree

2 files changed

+116
-20
lines changed

2 files changed

+116
-20
lines changed

docs/customservices.md

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ Smart cards provide specific integrations for external services. They display ad
44

55
Each service integration has different requirements and may need additional configuration parameters (see card list below).
66

7-
> [!WARNING]
7+
> [!WARNING]
88
> Your `config.yml` file is exposed at `/assets/config.yml` via HTTP. Any sensitive information (like API keys)
99
> in this file is visible to anyone who can access your Homer instance. Only include API keys if your Homer
1010
> instance is protected by authentication or access controls **or use a proxy like [`CORSair`](https://github.com/bastienwirtz/corsair)
11-
> to inject your credentials safely**, using environment variable on the server side.
11+
> to inject your credentials safely**, using environment variable on the server side.
1212
1313
Available services are located in `src/components/`:
1414

@@ -60,7 +60,7 @@ Available services are located in `src/components/`:
6060
- [Wallabag](#wallabag)
6161
- [What's Up Docker](#whats-up-docker)
6262

63-
> [!IMPORTANT]
63+
> [!IMPORTANT]
6464
> Smart cards that interact with external services are subject to CORS restrictions, therefore require one of the following:
6565
>
6666
> - All services hosted on the **same domain** as Homer (mydomain.tld/pihole, mydomain.tld/proxmox) to avoid cross-domain request entirely.
@@ -75,7 +75,7 @@ Available services are located in `src/components/`:
7575
- name: "My Service"
7676
type: "<type>"
7777
logo: "assets/tools/sample.png" # Optional
78-
url: https://my-service.url # Optional: Card link and API base url unless 'endpoint' is provided (see below)
78+
url: https://my-service.url # Optional: Card link and API base url unless 'endpoint' is provided (see below)
7979
endpoint: https://my-service-api.url # Optional: alternative base URL used to fetch service data when necessary.
8080
useCredentials: false # Optional: Override global proxy.useCredentials configuration.
8181
headers: # Optional: Override global proxy.headers configuration.
@@ -96,8 +96,8 @@ Displays AdGuard Home protection status and blocked query statistics.
9696

9797
> **Note**: If AdGuard Home’s web user is password-protected, you must pass Authorization HTTP header along with all requests. It can be done using a proxy or adding the following to the item configuration:
9898
>
99-
> ```yaml
100-
> headers:
99+
> ```yaml
100+
> headers:
101101
> Authorization: "Basic <base64-encoded for username:password>"
102102
> ```
103103

@@ -232,6 +232,7 @@ Displays status counts (up/down/grace) from your Healthchecks monitoring service
232232

233233
Displays Home Assistant instance status, version, location, and entity count.
234234

235+
**Basic configuration:**
235236
```yaml
236237
- name: "Home Assistant"
237238
type: "HomeAssistant"
@@ -252,8 +253,40 @@ http:
252253
cors_allowed_origins:
253254
- "http://homer.local:8080"
254255
- "https://your-homer-domain.com"
256+
```
257+
258+
**Custom sensors configuration:**
259+
```yaml
260+
- name: "Home Assistant"
261+
logo: "assets/tools/sample.png"
262+
url: "http://192.168.0.151/"
263+
type: "HomeAssistant"
264+
apikey: "<---insert-api-key-here--->"
265+
showUnits: true # Optional: Show units from Home Assistant (default: true)
266+
updateInterval: 30000 # Optional: Sensor refresh interval in ms (default: 30000)
267+
sensors: # Optional: Display custom sensors instead of default stats
268+
- id: "sensor.living_room_temperature"
269+
icon: "fas fa-home"
270+
- id: "sensor.bedroom_humidity"
271+
icon: "fas fa-bed"
272+
- id: "sensor.power_consumption"
273+
icon: "fas fa-bolt"
274+
- id: "sensor.outdoor_air_quality"
275+
icon: "fas fa-cloud-sun"
255276
```
256277

278+
**Configuration Options:**
279+
280+
- When `sensors` is provided, the service displays sensor readings with custom icons instead of the default version/entity information
281+
- `showUnits`: Controls whether to display units from Home Assistant (default: `true`)
282+
- `updateInterval`: How often to refresh sensor data in milliseconds (default: `30000`)
283+
- Sensor values are automatically formatted to 1 decimal place with units (when enabled)
284+
- Supports any numeric sensor type (temperature, humidity, power, etc.)
285+
- Falls back to original behavior if `sensors` is not configured
286+
287+
To create an API token on HomeAssistant, follow the [official documentation here](https://developers.home-assistant.io/docs/auth_api/#long-lived-access-token).
288+
To enable cors on HomeAssistant, edit your `configuration.yml` and add the IP of Homer to `https: cors_allowed_origins`
289+
257290
## Immich
258291

259292
Displays user count, photo/video counts, and storage usage from your Immich server.
@@ -302,7 +335,7 @@ The url must be the root url of Lidarr, Prowlarr, Readarr, Radarr or Sonarr appl
302335

303336
**API Key**: The Lidarr, Prowlarr, Readarr, Radarr or Sonarr API key can be found in `Settings` > `General`. It is needed to access the API.
304337

305-
> [!IMPORTANT]
338+
> [!IMPORTANT]
306339
> **Radarr API V3 support**: If you are using an older version of Radarr or Sonarr which don't support the new V3 api endpoints, add the following line to your service config `"legacyApi: true"`
307340

308341
## Linkding
@@ -386,7 +419,7 @@ Moonraker's API mimmicks a few of OctoPrint's endpoints which makes these servic
386419
endpoint: "https://my-service-api.url:port"
387420
apikey: "<---insert-api-key-here--->"
388421
display: "text" # 'text' or 'bar'. Default to `text`.
389-
422+
390423
```
391424

392425
## Olivetin
@@ -435,7 +468,7 @@ The following configuration is available for the OpenWeatherMap service:
435468
locationId: "2759794" # Optional: Specify OpenWeatherMap city ID for better accuracy
436469
units: "metric" # units to display temperature. Can be one of: metric, imperial, kelvin. Defaults to kelvin.
437470
background: "square" # choose which type of background you want behind the image. Can be one of: square, circle, none. Defaults to none.
438-
471+
439472
```
440473

441474
**Remarks:**
@@ -564,7 +597,7 @@ Displays container counts (running/dead/misc), version, and online status from y
564597

565598
## Proxmox
566599

567-
Displays status information of a Proxmox node (VMs running and disk, memory and cpu used).
600+
Displays status information of a Proxmox node (VMs running and disk, memory and cpu used).
568601

569602
```yaml
570603
- name: "Proxmox - Node"
@@ -632,7 +665,7 @@ for setting up rTorrent.
632665

633666
## SABnzbd
634667

635-
Displays the number of currently active downloads on your SABnzbd instance.
668+
Displays the number of currently active downloads on your SABnzbd instance.
636669

637670
```yaml
638671
- name: "SABnzbd"
@@ -677,7 +710,7 @@ Displays the number of currently active streams on you Plex instance.
677710
type: "Tautulli"
678711
logo: "assets/tools/sample.png"
679712
url: https://my-service.url
680-
checkInterval: 5000 # (Optional) Interval (in ms) for updating the status
713+
checkInterval: 5000 # (Optional) Interval (in ms) for updating the status
681714
apikey: "<---insert-api-key-here--->"
682715
```
683716

@@ -717,7 +750,7 @@ Displays Traefik.
717750
type: "Traefik"
718751
logo: "assets/tools/sample.png"
719752
url: "http://traefik.example.com"
720-
# basic_auth: "admin:password" # (Optional) Send Authorization header.
753+
# basic_auth: "admin:password" # (Optional) Send Authorization header.
721754
```
722755

723756
**Authentication**: If BasicAuth is set, credentials will be encoded in Base64 and sent as an Authorization header (`Basic <encoded_value>`). The value must be formatted as "admin:password".

src/components/services/HomeAssistant.vue

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,18 @@
66
<template v-if="item.subtitle">
77
{{ item.subtitle }}
88
</template>
9+
<template v-else-if="item.sensors && sensors.length > 0">
10+
<span class="sensors">
11+
<span
12+
v-for="(sensor, index) in sensors"
13+
:key="index"
14+
class="sensor"
15+
>
16+
<i :class="sensor.icon"></i>
17+
<span class="sensor-value">{{ sensor.value }}</span>
18+
</span>
19+
</span>
20+
</template>
921
<template v-else>
1022
{{ details }}
1123
</template>
@@ -35,6 +47,7 @@ export default {
3547
location_name: "",
3648
separator: " ",
3749
items: ["name", "version"],
50+
sensors: [],
3851
}),
3952
computed: {
4053
headers: function () {
@@ -70,16 +83,54 @@ export default {
7083
},
7184
},
7285
created() {
73-
this.fetchServerStatus().then(() => {
74-
if (!this.item.subtitle && this.status !== "dead") {
75-
if (this.item.items) this.items = this.item.items;
76-
if (this.item.separator) this.separator = this.item.separator;
86+
if (this.item.sensors) {
87+
// If sensors are configured, fetch sensor data
88+
this.fetchSensors();
7789
78-
this.fetchServerStats();
79-
}
80-
});
90+
// Set up configurable refresh interval (default 30 seconds)
91+
const updateInterval = parseInt(this.item.updateInterval, 10) || 30000;
92+
setInterval(() => this.fetchSensors(), updateInterval);
93+
} else {
94+
// Original behavior for status/stats
95+
this.fetchServerStatus().then(() => {
96+
if (!this.item.subtitle && this.status !== "dead") {
97+
if (this.item.items) this.items = this.item.items;
98+
if (this.item.separator) this.separator = this.item.separator;
99+
100+
this.fetchServerStats();
101+
}
102+
});
103+
}
81104
},
82105
methods: {
106+
fetchSensors: async function () {
107+
const headers = this.headers;
108+
109+
try {
110+
const response = await this.fetch("/api/states", { headers });
111+
112+
// Use configurable sensors from item.sensors
113+
this.sensors = this.item.sensors
114+
.map((sensorConfig) => {
115+
const match = response.find((s) => s.entity_id === sensorConfig.id);
116+
if (match && !isNaN(parseFloat(match.state))) {
117+
const value = parseFloat(match.state).toFixed(1);
118+
const unit = match.attributes?.unit_of_measurement || "";
119+
const showUnits = this.item.showUnits !== false; // Default to true
120+
return {
121+
icon: sensorConfig.icon,
122+
value: (showUnits && unit) ? `${value}${unit}` : value,
123+
};
124+
} else {
125+
return null;
126+
}
127+
})
128+
.filter(Boolean); // Remove null entries
129+
} catch (error) {
130+
console.error("Failed to fetch sensors:", error);
131+
this.sensors = [];
132+
}
133+
},
83134
fetchServerStatus: async function () {
84135
const headers = this.headers;
85136
@@ -151,4 +202,16 @@ export default {
151202
border-radius: 7px;
152203
}
153204
}
205+
206+
.sensors {
207+
display: flex;
208+
flex-wrap: wrap;
209+
gap: 8px;
210+
}
211+
212+
.sensor {
213+
display: inline-flex;
214+
align-items: center;
215+
gap: 4px;
216+
}
154217
</style>

0 commit comments

Comments
 (0)