Wallboard for displaying information from Home Assistant
  • C# 48.8%
  • TypeScript 41.7%
  • SCSS 5.9%
  • HTML 3.3%
  • Dockerfile 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Nathan van Dael e5bf932dce
All checks were successful
Auto Build & Push Multi-Arch Docker Image / build-and-push (push) Successful in 3m12s
feat: Implement ActionController for Home Assistant actions
- Added ActionController to handle various actions on Home Assistant entities.
- Introduced ActionRequest record to encapsulate action parameters.
- Implemented Perform method to validate requests and call Home Assistant services.

feat: Create DashboardController for rendering Home Assistant dashboard

- Added DashboardController to serve the Home Assistant dashboard.
- Implemented Get method to retrieve and translate dashboard views and screensavers.
- Added States method to return the current states of all entities on the dashboard.
- Implemented Calendar method to fetch calendar events for specified entities.

feat: Introduce LocalController for serving local images

- Added LocalController to serve images from Home Assistant's local directory.
- Implemented Get method to validate requests and return local images.

feat: Develop DashboardModel for managing dashboard configuration

- Created DashboardModel to represent the Home Assistant dashboard structure.
- Implemented methods to build and expand the dashboard configuration.
- Added functionality to handle auto-entities and entity filtering.

feat: Establish DashboardStore for managing dashboard state and entities

- Introduced DashboardStore to manage the state of the dashboard and its entities.
- Implemented methods to set and retrieve dashboard configuration, states, and translations.

feat: Add Json utility methods for handling JSON nodes

- Created Json static class with extension methods for easier JSON node manipulation.
- Added methods to retrieve text, numbers, booleans, and lists of texts from JSON nodes.
2026-11-06 13:59:16 +01:00
.forgejo/workflows
client feat: Implement ActionController for Home Assistant actions 2026-11-06 13:59:16 +01:00
server feat: Implement ActionController for Home Assistant actions 2026-11-06 13:59:16 +01:00
.dockerignore
.env.example feat: Implement ActionController for Home Assistant actions 2026-11-06 13:59:16 +01:00
.gitignore
docker-compose.yml
Dockerfile
InternalDashboard.slnx
LICENSE
README.md feat: Implement ActionController for Home Assistant actions 2026-11-06 13:59:16 +01:00

Internal Dashboard

A fullscreen wallboard for displaying Home Assistant sensor data, camera feeds, and weather forecasts. Designed to run on a wall-mounted tablet or monitor.

  • client/: Angular frontend: the wallboard itself
  • server/InternalDashboard.Api/: ASP.NET Core (.NET 10): talks to Home Assistant (REST and WebSocket) and LibreTranslate, and serves the built Angular app. The Home Assistant token stays here: the browser never sees it.

Features

  • Live clock and date
  • Outdoor and indoor temperature and humidity (top left), each with a smooth temperature history sparkline (configurable time range)
  • Click the indoor climate card to open a modal with the temperature and humidity of other rooms (also with sparklines)
  • Click any temperature tile (outdoor, indoor, or a room) to open a large history chart with labelled X (time) and Y (value) axes; the indoor tile stays clickable for its own chart when no rooms are configured
  • Or, with HASS_DASHBOARD, a dashboard built in Home Assistant's own editor, with its wallpanel screensaver (see Home Assistant dashboard)
  • Configurable quick-access entities in the top-right bar
  • Camera feed with a subtle blue-tint overlay and optional location dot
  • Daily and hourly weather forecasts with precipitation charts (equal-height, side by side)
  • Toggle support for switches, lights, automations, and scripts
  • Real-time updates via the Home Assistant WebSocket API, relayed by the server (60 s polling fallback)
  • Multi-language support (Dutch, English, German) — auto-detected from the browser, with a manual switcher; custom entity/room names are auto-translated too
  • PWA support — installable on iPad/Android home screen
  • Screen wake lock to keep the display active (Wake Lock API + silent video fallback for iOS 15.8)
  • Subtle refresh button in the bottom-right corner

Compatibility

Tested with Home Assistant version 2026.6

Getting started

1. Configure environment

Copy .env.example to .env and fill in your values:

cp .env.example .env

2. Run with Docker

docker compose up -d --build

The dashboard is available at http://localhost:8080. docker-compose.yml also starts the libretranslate service used for the name auto-translation feature described under Translation.

Environment variables

Two variables are required; all others are optional.

General

Variable Required Description
APP_NAME — Display name shown in the browser tab, home screen, and PWA manifest. Defaults to Dashboard.

Home Assistant connection

Variable Required Description
HASS_URL ✓ Base URL of your Home Assistant instance, without trailing slash. Example: http://homeassistant.local:8123
HASS_TOKEN ✓ Long-lived access token. Create one via Profile → Security → Long-lived access tokens.

Security note: The token stays on the server: the browser talks only to the dashboard, which talks to Home Assistant. Still, anyone who can open the dashboard can see the configured entities and use its switches — only expose it on a trusted local network, never on the public internet.

Home Assistant dashboard

Variable Required Description
HASS_DASHBOARD — A dashboard from Home Assistant to show instead of the cards configured below: its url_path, optionally with a view (its number or path), e.g. dashboard-keuken or dashboard-keuken/0. The address from the browser (https://ha.example/dashboard-keuken/0) works too; lovelace is the default dashboard. With it, HASS_ENTITIES up to HASS_WEATHER_HOURLY are ignored.

Without HASS_DASHBOARD the dashboard shows the cards from the settings below, as always. With it, you build the dashboard in Home Assistant's own editor and this app shows it in its own style: the server reads the dashboard over the WebSocket API and loads it again whenever it is saved, so changes appear on the wall display right away.

Shown are a sections view (sections side by side, as many as fit, each a grid of 12 columns) or the cards of an older view, with these cards:

  • Home Assistant's tile (with the features target-temperature, climate-preset-modes and climate-hvac-modes), heading (with badges), entities, sensor, button, picture-elements (a camera or picture with images, icons and states on it), grid, vertical-stack, horizontal-stack, conditional and weather-forecast
  • the custom cards auto-entities, date-time-card, week-planner-card, weather-forecast-card and neerslag-card
  • the wallpanel screensaver of the dashboard: after its idle_time its cards cover the screen until the next touch

Cards follow their visibility conditions, names such as { type: area } show the area's name, and states, icons and colours are Home Assistant's own (in the visitor's language). card_mod styling is left out: it is CSS for Home Assistant's own pages. Other card types are left out as well.

Only what the dashboard's cards offer can be done through the app, so the wall display cannot switch everything in Home Assistant: a tap action toggle, the switch, run button or list of an entities row, and the features of a tile (only the presets and modes it lists). Likewise only the entities, cameras, calendars and /local/ images on the dashboard can be read.

Entities

Variable Required Description
HASS_ENTITIES — Comma-separated list of entity IDs to display as cards on the dashboard. If omitted, no general entity cards are shown.
HASS_ENTITY_NAMES — Custom display names for HASS_ENTITIES, in the same order.
HASS_OUTDOOR_TEMP — Entity ID for outdoor temperature, shown prominently in the top left.
HASS_OUTDOOR_HUMIDITY — Entity ID for outdoor humidity, shown next to the outdoor temperature.
HASS_INDOOR_TEMP — Entity ID for indoor temperature, shown next to the outdoor climate card in the top left.
HASS_INDOOR_HUMIDITY — Entity ID for indoor humidity, shown next to the indoor temperature.
HASS_ROOM_NAMES — Comma-separated room labels for the rooms modal (opened by clicking the indoor climate card).
HASS_ROOM_TEMPS — Comma-separated temperature entity IDs for the rooms modal, in the same order as HASS_ROOM_NAMES.
HASS_ROOM_HUMIDITY — Comma-separated humidity entity IDs for the rooms modal, in the same order as HASS_ROOM_NAMES.
HASS_HISTORY_HOURS 24 Number of hours of history shown in the temperature sparklines (1–168).
HASS_TOP_ENTITIES — Comma-separated entity IDs shown in the fixed top-right bar. Supports sensors, switches, lights, scripts, and automations. Text is truncated with an ellipsis if too long.
HASS_TOP_ENTITY_NAMES — Custom display names for HASS_TOP_ENTITIES, in the same order.

Only the entities configured here can be read or switched through the dashboard: a switch in HASS_ENTITIES or HASS_TOP_ENTITIES can be toggled, and the climate sensors have a history.

Camera

Variable Required Description
HASS_CAMERA — Entity ID of a camera to display (e.g. camera.buienradar). The image is rendered square with a grayscale + blue-tint overlay.
HASS_CAMERA_DOT — Position of a small location dot on the camera image, as x,y in percentage (0–100). Example: 52,48 places the dot slightly left of centre. Useful for marking your location on a rain radar.

Weather

Variable Required Description
HASS_WEATHER — Entity ID of a weather integration for the daily forecast card (up to 8 days).
HASS_WEATHER_HOURLY — Entity ID of a weather integration for the hourly forecast card (up to 24 hours).

The precipitation chart prefers the precipitation amount (mm) from the forecast. If the integration does not provide it (e.g. KNMI's daily forecast only exposes precipitation_probability), the chart falls back to showing the probability (%) instead. If neither field is present, no bars are drawn. Note that some integrations (e.g. Buienradar) do not support an hourly forecast at all — keep HASS_WEATHER_HOURLY on an entity that does.

Translation

Variable Required Description
HASS_NAMES_SOURCE_LANG nl Language the names above (HASS_ENTITY_NAMES, HASS_TOP_ENTITY_NAMES, HASS_ROOM_NAMES) are written in. Used as the source language when auto-translating them for visitors using another language.
TRANSLATE_ENABLED true Set to false to disable auto-translation of the names above (they will then be shown as-is regardless of the selected language).
TRANSLATE_URL — URL of a LibreTranslate instance. Use http://libretranslate:5000, the libretranslate service in docker-compose.yml, unless you run your own instance elsewhere.
TRANSLATE_API_KEY — Optional API key, only needed if your LibreTranslate instance requires one.
TRANSLATE_IGNORE_WORDS — Comma-separated words that should never be translated (e.g. people's names used in room names, like Minna,Ida). Matched as whole words, case-insensitively.
STORAGE_DIR /app/storage Where the translation cache is kept.

For sensors (any entity outside the toggle domains switch, light, input_boolean, fan, automation, script), the state value itself is also auto-translated by default — useful for text-based sensors such as a waste collection calendar. Numbers, unavailable and unknown are never sent for translation, and toggle entities are never touched, since their raw on/off state drives the UI and must stay exact.

The translation cache is warmed automatically in the background every time the app starts, so a fresh or restarted container is never slow on its first non-default-language request — it briefly waits for LibreTranslate to become reachable first, in case it's still downloading its language models. Changes to .env take effect when the container is recreated (docker compose up -d), which warms the cache again. Without warming, translations still happen automatically on first use.

Language

The dashboard language is automatically detected from the browser, and can be changed manually via the globe button in the bottom-right corner (cycles through the supported languages and remembers the choice in a cookie). Without a choice and without a usable browser language (some kiosk browsers send none), the dashboard uses HASS_NAMES_SOURCE_LANG. Supported languages:

Code Language
nl Dutch
en English (default fallback)
de German

Translation files are located in client/public/i18n/. To add a language, create client/public/i18n/<code>.json following the structure of an existing file, add the code to LANGUAGES in i18n.ts and to Supported in Languages.cs, and load its model in the libretranslate service (LT_LOAD_ONLY in docker-compose.yml).

Custom entity and room names configured via .env (HASS_ENTITY_NAMES, HASS_TOP_ENTITY_NAMES, HASS_ROOM_NAMES) are written in a single language — see Translation above — and machine-translated on the fly for visitors using a different language, via the self-hosted libretranslate service. Results are cached on disk (translations-cache.json in STORAGE_DIR, kept in the storage volume) so each name is only translated once; a cache file from the PHP version can be copied in as is. If the translation service is unreachable or disabled, the original name is shown unchanged.

Real-time updates

The server keeps one connection to the Home Assistant WebSocket API and subscribes to state_changed events. When an entity shown on the dashboard changes, it tells every open dashboard through server-sent events (/api/events), and the dashboards update the cards in place without rebuilding the page, which avoids camera image flicker. A 60-second full refresh runs in parallel as a safety net. The server reconnects to Home Assistant after 5 seconds if the connection is lost; the browsers reconnect to the server on their own. With HASS_DASHBOARD the server also subscribes to lovelace_updated and the registry events, and the browsers load the dashboard again when it is saved in Home Assistant.

Troubleshooting

Inspecting the weather forecast

If a forecast card is empty or the precipitation chart shows no bars, you can inspect exactly what Home Assistant returns by adding ?debug to the forecast endpoint in your browser:

URL Forecast
http://<host>:8080/api/forecast?debug Daily (HASS_WEATHER)
http://<host>:8080/api/forecast?hourly&debug Hourly (HASS_WEATHER_HOURLY)

The response highlights the most useful diagnostics at the top:

  • available_keys — the fields each forecast entry exposes (look for precipitation and/or precipitation_probability).
  • has_precipitation — whether the precipitation (mm) field is present.
  • first_entry — the full first forecast entry.
  • raw_service_response — the complete weather.get_forecasts response.

This is handy when switching between weather integrations, since each exposes a different set of fields.

PWA / tablet installation

The dashboard can be installed as a Progressive Web App on iPad or Android:

  1. Open the dashboard in Safari (iPad) or Chrome (Android).
  2. Tap the share icon → Add to Home Screen.
  3. The app opens fullscreen without browser chrome.

The fullscreen button is automatically hidden when running in standalone (PWA) mode or when the Fullscreen API is unavailable.

Keeping the screen awake

Platform Method
Modern browsers Screen Wake Lock API
iOS 15.8 and older Silent looping H.264 video (nosleep.mp4) activated on first touch

On iPadOS 15 and older, also set Settings → Display & Brightness → Auto-Lock → Never for fully reliable always-on behaviour.

Local development

# API on http://localhost:5380; reads the settings from .env
cp .env.example .env
dotnet run --project server/InternalDashboard.Api

# Frontend in a second terminal; open http://localhost:4200
cd client
npm install
npm start

For translation during development, run LibreTranslate as well (docker compose up -d libretranslate) and set TRANSLATE_URL to an address your machine can reach, or set TRANSLATE_ENABLED=false.

Frontend tests: cd client && npm test. Server tests: dotnet test InternalDashboard.slnx.

API

Method Path Description
GET /api/config App name, the visitor's language, camera settings
GET /api/states The entity cards (HASS_ENTITIES), names and sensor texts translated
GET /api/states/top The top-right bar (HASS_TOP_ENTITIES)
GET /api/states/outdoor, /api/states/indoor Temperature and humidity
GET /api/states/rooms The rooms of the rooms modal
GET /api/history?entity=…&hours=…&points=… History of a climate sensor, downsampled
GET /api/forecast, /api/forecast?hourly Daily and hourly forecast (&debug, see above)
GET /api/camera The current camera image
POST /api/toggle Toggle a switch on the dashboard: { "entityId": "switch.…" }
GET /api/events Server-sent events: the id of a dashboard entity whenever it changes; event: dashboard when the HASS_DASHBOARD dashboard was saved
GET /api/dashboard With HASS_DASHBOARD: its view and screensaver, names translated
GET /api/dashboard/states With HASS_DASHBOARD: the states of its entities, with Home Assistant's texts and icons
GET /api/dashboard/calendar?entity=…&start=…&end=… With HASS_DASHBOARD: the events of a calendar on it
POST /api/action With HASS_DASHBOARD: what a card offers: { "entityId": "climate.…", "service": "set_temperature", "value": 21 }
GET /api/local/… With HASS_DASHBOARD: an image from Home Assistant's /local/ on it
GET /manifest.webmanifest PWA manifest with APP_NAME

The old addresses /index.php and /manifest.php redirect permanently to / and /manifest.webmanifest.

Docker

The Dockerfile builds the Angular app and the server into one image; the server serves the Angular app.

Property Value
Base image mcr.microsoft.com/dotnet/aspnet:10.0
Exposed port 8080
User non-root ($APP_UID)
Volume /app/storage (translation cache; optional)
services:
  internal-dashboard:
    build: .
    container_name: internal-dashboard
    env_file:
      - .env
    ports:
      - "8080:8080"
    volumes:
      - storage:/app/storage

The docker-compose.yml file adds the libretranslate service. It loads only the en, nl, and de models to keep resource usage down, and downloads them on first start — translation falls back to the untranslated name until that finishes.

The responses carry a strict Content-Security-Policy (no inline scripts or styles without the request's nonce; everything from the dashboard itself). Framing is allowed, so the dashboard can be shown inside Home Assistant.

CI/CD

The Forgejo workflow (.forgejo/workflows/docker-publish.yml) builds the image on every push to main (and monthly) and pushes it with a version tag and latest to the Forgejo container registry. This needs the repository secret REGISTRY_TOKEN.