diff --git a/homeassistant/README.md b/homeassistant/README.md
new file mode 100644
index 0000000..2e346bf
--- /dev/null
+++ b/homeassistant/README.md
@@ -0,0 +1,315 @@
+# Hyprland Monitor Control Integration for Home Assistant
+
+This custom integration allows you to control your Hyprland desktop monitors directly from Home Assistant. It connects to your Hyprmonitors Rust server to provide full monitor control capabilities.
+
+## Features
+
+- **Monitor Status Sensors**: Real-time status of all monitors (on/off/mixed)
+- **Individual Monitor Switches**: Control each monitor separately
+- **Master Switch**: Turn all monitors on/off at once
+- **Custom Services**: Advanced automation capabilities
+- **Auto-discovery**: Automatically detects available monitors
+- **Real-time Updates**: Monitor status updates every 30 seconds
+
+## Prerequisites
+
+1. **Hyprmonitors Server**: The Rust server must be running and accessible
+2. **Home Assistant**: Version 2023.1.0 or newer
+3. **Network Access**: Home Assistant must be able to reach your Hyprmonitors server
+
+## Installation
+
+### Method 1: Manual Installation
+
+1. Create the custom components directory in your Home Assistant configuration:
+ ```bash
+ mkdir -p config/custom_components/hyprmonitors
+ ```
+
+2. Copy all files from this directory to your Home Assistant custom components:
+ ```bash
+ cp -r custom_components/hyprmonitors/* config/custom_components/hyprmonitors/
+ ```
+
+3. Restart Home Assistant
+
+### Method 2: HACS (Recommended when available)
+
+1. Add this repository to HACS as a custom repository
+2. Search for "Hyprland Monitor Control" in HACS
+3. Install the integration
+4. Restart Home Assistant
+
+## Configuration
+
+### Via UI (Recommended)
+
+1. Go to **Settings** → **Devices & Services**
+2. Click **Add Integration**
+3. Search for "Hyprland Monitor Control"
+4. Enter your Hyprmonitors server details:
+ - **Host**: IP address or hostname (default: localhost)
+ - **Port**: Port number (default: 3000)
+5. Click **Submit**
+
+The integration will automatically discover your monitors and create entities.
+
+### Via configuration.yaml (Legacy)
+
+```yaml
+hyprmonitors:
+ host: localhost
+ port: 3000
+```
+
+## Entities Created
+
+### Sensors
+
+- **`sensor.hyprmonitors_monitor_status`**: Overall status of all monitors
+ - States: `all_on`, `all_off`, `mixed`, `unavailable`
+ - Attributes: total monitors, monitors on/off count, monitor details
+
+- **`sensor.hyprmonitors_[monitor_name]_status`**: Individual monitor status
+ - States: `on`, `off`, `unknown`, `unavailable`
+ - Attributes: monitor name, friendly name
+
+### Switches
+
+- **`switch.hyprmonitors_all_monitors`**: Master switch for all monitors
+ - Turn all monitors on/off simultaneously
+ - Shows mixed state when some monitors are on/off
+
+- **`switch.hyprmonitors_[monitor_name]`**: Individual monitor switches
+ - Control specific monitors by name
+ - Examples: `switch.hyprmonitors_dp_1`, `switch.hyprmonitors_hdmi_a_1`
+
+## Services
+
+The integration provides several services for advanced automation:
+
+### `hyprmonitors.turn_on_monitor`
+Turn on a specific monitor by name.
+
+```yaml
+service: hyprmonitors.turn_on_monitor
+data:
+ monitor: "DP-1"
+```
+
+### `hyprmonitors.turn_off_monitor`
+Turn off a specific monitor by name.
+
+```yaml
+service: hyprmonitors.turn_off_monitor
+data:
+ monitor: "HDMI-A-1"
+```
+
+### `hyprmonitors.turn_on_all_monitors`
+Turn on all monitors.
+
+```yaml
+service: hyprmonitors.turn_on_all_monitors
+```
+
+### `hyprmonitors.turn_off_all_monitors`
+Turn off all monitors.
+
+```yaml
+service: hyprmonitors.turn_off_all_monitors
+```
+
+## Automation Examples
+
+### Turn off monitors when away
+
+```yaml
+automation:
+ - alias: "Turn off monitors when away"
+ trigger:
+ - platform: state
+ entity_id: person.your_name
+ to: "not_home"
+ action:
+ - service: hyprmonitors.turn_off_all_monitors
+
+ - alias: "Turn on monitors when home"
+ trigger:
+ - platform: state
+ entity_id: person.your_name
+ to: "home"
+ action:
+ - service: hyprmonitors.turn_on_all_monitors
+```
+
+### Scheduled monitor control
+
+```yaml
+automation:
+ - alias: "Turn off monitors at night"
+ trigger:
+ - platform: time
+ at: "23:00:00"
+ action:
+ - service: switch.turn_off
+ entity_id: switch.hyprmonitors_all_monitors
+
+ - alias: "Turn on monitors in the morning"
+ trigger:
+ - platform: time
+ at: "07:00:00"
+ condition:
+ - condition: state
+ entity_id: person.your_name
+ state: "home"
+ action:
+ - service: switch.turn_on
+ entity_id: switch.hyprmonitors_all_monitors
+```
+
+### Smart meeting room control
+
+```yaml
+automation:
+ - alias: "Turn off secondary monitor during meetings"
+ trigger:
+ - platform: calendar
+ event: start
+ entity_id: calendar.work
+ condition:
+ - condition: template
+ value_template: "{{ 'meeting' in trigger.calendar_event.summary.lower() }}"
+ action:
+ - service: hyprmonitors.turn_off_monitor
+ data:
+ monitor: "DP-2" # Secondary monitor
+
+ - alias: "Turn on secondary monitor after meetings"
+ trigger:
+ - platform: calendar
+ event: end
+ entity_id: calendar.work
+ condition:
+ - condition: template
+ value_template: "{{ 'meeting' in trigger.calendar_event.summary.lower() }}"
+ action:
+ - service: hyprmonitors.turn_on_monitor
+ data:
+ monitor: "DP-2"
+```
+
+## Dashboard Cards
+
+### Monitor Status Card
+
+```yaml
+type: entities
+entities:
+ - entity: sensor.hyprmonitors_monitor_status
+ name: Monitor Status
+ - entity: switch.hyprmonitors_all_monitors
+ name: All Monitors
+title: Monitor Control
+```
+
+### Individual Monitor Controls
+
+```yaml
+type: glance
+entities:
+ - entity: switch.hyprmonitors_dp_1
+ name: Main Monitor
+ icon: mdi:monitor
+ - entity: switch.hyprmonitors_hdmi_a_1
+ name: Secondary
+ icon: mdi:monitor-speaker
+ - entity: switch.hyprmonitors_all_monitors
+ name: All Monitors
+ icon: mdi:monitor-multiple
+title: Monitor Control
+```
+
+### Monitor Status with Attributes
+
+```yaml
+type: custom:auto-entities
+card:
+ type: entities
+ title: Monitor Details
+filter:
+ include:
+ - entity_id: "sensor.*monitor*status"
+ options:
+ type: custom:multiple-entity-row
+ name: "{{ state_attr(config.entity, 'friendly_name') }}"
+ show_state: true
+ entities:
+ - attribute: monitors_on
+ name: "On"
+ - attribute: monitors_off
+ name: "Off"
+ - attribute: total_monitors
+ name: "Total"
+```
+
+## Troubleshooting
+
+### Connection Issues
+
+1. **Cannot connect to server**:
+ - Verify the Hyprmonitors server is running: `curl http://localhost:3000/health`
+ - Check host and port configuration
+ - Ensure firewall allows connections
+
+2. **Monitors not detected**:
+ - Check if Hyprland is running
+ - Verify monitors are detected: `hyprctl monitors`
+ - Restart the Hyprmonitors server
+
+3. **Integration not loading**:
+ - Check Home Assistant logs for errors
+ - Verify all files are copied correctly
+ - Restart Home Assistant completely
+
+### Debug Logging
+
+Add to your `configuration.yaml` to enable debug logging:
+
+```yaml
+logger:
+ default: info
+ logs:
+ custom_components.hyprmonitors: debug
+```
+
+### Monitor Names
+
+To find your exact monitor names, check:
+- Hyprmonitors server logs on startup
+- Run `hyprctl monitors` in terminal
+- Check the sensor attributes in Home Assistant
+
+Common patterns:
+- **DisplayPort**: `DP-1`, `DP-2`, etc.
+- **HDMI**: `HDMI-A-1`, `HDMI-A-2`, etc.
+- **Laptop screen**: `eDP-1`
+
+## Configuration Options
+
+### Advanced Configuration
+
+The integration supports these advanced options (configure via UI):
+
+- **Scan Interval**: How often to check monitor status (default: 30 seconds)
+- **Timeout**: API request timeout (default: 10 seconds)
+
+## Support
+
+- **Issues**: Report bugs and feature requests on GitHub
+- **Documentation**: Check the main Hyprmonitors README
+- **Community**: Join the Home Assistant Community forums
+
+## License
+
+This integration is part of the Hyprmonitors project and follows the same license terms.
\ No newline at end of file
diff --git a/homeassistant/custom_components/hyprmonitors/__init__.py b/homeassistant/custom_components/hyprmonitors/__init__.py
new file mode 100644
index 0000000..60be07d
--- /dev/null
+++ b/homeassistant/custom_components/hyprmonitors/__init__.py
@@ -0,0 +1,206 @@
+"""Hyprland Monitor Control integration for Home Assistant."""
+from __future__ import annotations
+
+import asyncio
+import logging
+from typing import Any
+
+import aiohttp
+import async_timeout
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.const import Platform
+from homeassistant.core import HomeAssistant
+from homeassistant.exceptions import ConfigEntryNotReady
+from homeassistant.helpers.aiohttp_client import async_get_clientsession
+import voluptuous as vol
+from homeassistant.helpers import config_validation as cv
+
+from .const import DOMAIN, PLATFORMS, SERVICE_TURN_ON_MONITOR, SERVICE_TURN_OFF_MONITOR, SERVICE_TURN_ON_ALL, SERVICE_TURN_OFF_ALL
+
+_LOGGER = logging.getLogger(__name__)
+
+type HyprmonitorsConfigEntry = ConfigEntry[HyprmonitorsData]
+
+
+class HyprmonitorsData:
+ """Data for Hyprmonitors integration."""
+
+ def __init__(self, session: aiohttp.ClientSession, host: str, port: int) -> None:
+ """Initialize the data object."""
+ self.session = session
+ self.host = host
+ self.port = port
+ self.base_url = f"http://{host}:{port}"
+
+ async def async_get_monitors(self) -> dict[str, Any]:
+ """Get monitor status from the API."""
+ url = f"{self.base_url}/monitors/status"
+ try:
+ async with async_timeout.timeout(10):
+ async with self.session.get(url) as response:
+ response.raise_for_status()
+ data = await response.json()
+ return data.get("monitors", {})
+ except asyncio.TimeoutError as err:
+ _LOGGER.error("Timeout connecting to Hyprmonitors API at %s", url)
+ raise ConfigEntryNotReady("Timeout connecting to API") from err
+ except aiohttp.ClientError as err:
+ _LOGGER.error("Error connecting to Hyprmonitors API at %s: %s", url, err)
+ raise ConfigEntryNotReady("Error connecting to API") from err
+
+ async def async_turn_monitor_on(self, monitor: str) -> bool:
+ """Turn on a monitor."""
+ url = f"{self.base_url}/monitors/{monitor}/on"
+ try:
+ async with async_timeout.timeout(10):
+ async with self.session.post(url) as response:
+ response.raise_for_status()
+ data = await response.json()
+ return data.get("success", False)
+ except (asyncio.TimeoutError, aiohttp.ClientError) as err:
+ _LOGGER.error("Error turning on monitor %s: %s", monitor, err)
+ return False
+
+ async def async_turn_monitor_off(self, monitor: str) -> bool:
+ """Turn off a monitor."""
+ url = f"{self.base_url}/monitors/{monitor}/off"
+ try:
+ async with async_timeout.timeout(10):
+ async with self.session.post(url) as response:
+ response.raise_for_status()
+ data = await response.json()
+ return data.get("success", False)
+ except (asyncio.TimeoutError, aiohttp.ClientError) as err:
+ _LOGGER.error("Error turning off monitor %s: %s", monitor, err)
+ return False
+
+ async def async_turn_all_monitors_on(self) -> bool:
+ """Turn on all monitors."""
+ url = f"{self.base_url}/monitors/on"
+ try:
+ async with async_timeout.timeout(10):
+ async with self.session.post(url) as response:
+ response.raise_for_status()
+ data = await response.json()
+ return data.get("success", False)
+ except (asyncio.TimeoutError, aiohttp.ClientError) as err:
+ _LOGGER.error("Error turning on all monitors: %s", err)
+ return False
+
+ async def async_turn_all_monitors_off(self) -> bool:
+ """Turn off all monitors."""
+ url = f"{self.base_url}/monitors/off"
+ try:
+ async with async_timeout.timeout(10):
+ async with self.session.post(url) as response:
+ response.raise_for_status()
+ data = await response.json()
+ return data.get("success", False)
+ except (asyncio.TimeoutError, aiohttp.ClientError) as err:
+ _LOGGER.error("Error turning off all monitors: %s", err)
+ return False
+
+ async def async_test_connection(self) -> bool:
+ """Test connection to the API."""
+ url = f"{self.base_url}/health"
+ try:
+ async with async_timeout.timeout(5):
+ async with self.session.get(url) as response:
+ response.raise_for_status()
+ data = await response.json()
+ return data.get("success", False)
+ except (asyncio.TimeoutError, aiohttp.ClientError) as err:
+ _LOGGER.debug("Connection test failed: %s", err)
+ return False
+
+
+async def async_setup_entry(hass: HomeAssistant, entry: HyprmonitorsConfigEntry) -> bool:
+ """Set up Hyprmonitors from a config entry."""
+ host = entry.data["host"]
+ port = entry.data["port"]
+
+ session = async_get_clientsession(hass)
+ data = HyprmonitorsData(session, host, port)
+
+ # Test connection
+ if not await data.async_test_connection():
+ raise ConfigEntryNotReady("Unable to connect to Hyprmonitors API")
+
+ # Store the data object in the config entry
+ entry.runtime_data = data
+
+ # Forward the setup to the sensor and switch platforms
+ await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
+
+ # Register services
+ await async_setup_services(hass, data)
+
+ return True
+
+
+async def async_unload_entry(hass: HomeAssistant, entry: HyprmonitorsConfigEntry) -> bool:
+ """Unload a config entry."""
+ return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
+
+
+async def async_reload_entry(hass: HomeAssistant, entry: HyprmonitorsConfigEntry) -> None:
+ """Reload config entry."""
+ await async_unload_entry(hass, entry)
+ await async_setup_entry(hass, entry)
+
+
+async def async_setup_services(hass: HomeAssistant, api: HyprmonitorsData) -> None:
+ """Set up services for Hyprmonitors."""
+
+ async def turn_on_monitor_service(call) -> None:
+ """Service to turn on a specific monitor."""
+ monitor = call.data.get("monitor")
+ success = await api.async_turn_monitor_on(monitor)
+ if not success:
+ _LOGGER.error("Failed to turn on monitor %s", monitor)
+
+ async def turn_off_monitor_service(call) -> None:
+ """Service to turn off a specific monitor."""
+ monitor = call.data.get("monitor")
+ success = await api.async_turn_monitor_off(monitor)
+ if not success:
+ _LOGGER.error("Failed to turn off monitor %s", monitor)
+
+ async def turn_on_all_monitors_service(call) -> None:
+ """Service to turn on all monitors."""
+ success = await api.async_turn_all_monitors_on()
+ if not success:
+ _LOGGER.error("Failed to turn on all monitors")
+
+ async def turn_off_all_monitors_service(call) -> None:
+ """Service to turn off all monitors."""
+ success = await api.async_turn_all_monitors_off()
+ if not success:
+ _LOGGER.error("Failed to turn off all monitors")
+
+ # Register services
+ hass.services.async_register(
+ DOMAIN,
+ SERVICE_TURN_ON_MONITOR,
+ turn_on_monitor_service,
+ schema=vol.Schema({vol.Required("monitor"): cv.string}),
+ )
+
+ hass.services.async_register(
+ DOMAIN,
+ SERVICE_TURN_OFF_MONITOR,
+ turn_off_monitor_service,
+ schema=vol.Schema({vol.Required("monitor"): cv.string}),
+ )
+
+ hass.services.async_register(
+ DOMAIN,
+ SERVICE_TURN_ON_ALL,
+ turn_on_all_monitors_service,
+ )
+
+ hass.services.async_register(
+ DOMAIN,
+ SERVICE_TURN_OFF_ALL,
+ turn_off_all_monitors_service,
+ )
diff --git a/homeassistant/custom_components/hyprmonitors/config_flow.py b/homeassistant/custom_components/hyprmonitors/config_flow.py
new file mode 100644
index 0000000..7c82045
--- /dev/null
+++ b/homeassistant/custom_components/hyprmonitors/config_flow.py
@@ -0,0 +1,134 @@
+"""Config flow for Hyprland Monitor Control integration."""
+from __future__ import annotations
+
+import logging
+from typing import Any
+
+import aiohttp
+import async_timeout
+import voluptuous as vol
+from homeassistant import config_entries
+from homeassistant.const import CONF_HOST, CONF_PORT
+from homeassistant.core import HomeAssistant
+from homeassistant.data_entry_flow import FlowResult
+from homeassistant.exceptions import HomeAssistantError
+from homeassistant.helpers.aiohttp_client import async_get_clientsession
+
+from .const import DEFAULT_HOST, DEFAULT_PORT, DOMAIN
+
+_LOGGER = logging.getLogger(__name__)
+
+STEP_USER_DATA_SCHEMA = vol.Schema(
+ {
+ vol.Required(CONF_HOST, default=DEFAULT_HOST): str,
+ vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
+ }
+)
+
+
+async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
+ """Validate the user input allows us to connect.
+
+ Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
+ """
+ host = data[CONF_HOST]
+ port = data[CONF_PORT]
+
+ session = async_get_clientsession(hass)
+ url = f"http://{host}:{port}/health"
+
+ try:
+ async with async_timeout.timeout(10):
+ async with session.get(url) as response:
+ if response.status != 200:
+ raise CannotConnect
+
+ result = await response.json()
+ if not result.get("success", False):
+ raise CannotConnect
+
+ except aiohttp.ClientError as err:
+ _LOGGER.error("Error connecting to Hyprmonitors API: %s", err)
+ raise CannotConnect from err
+ except Exception as err:
+ _LOGGER.exception("Unexpected exception")
+ raise InvalidAuth from err
+
+ # Try to get monitor list to verify full functionality
+ try:
+ async with async_timeout.timeout(10):
+ async with session.get(f"http://{host}:{port}/monitors/status") as response:
+ if response.status != 200:
+ raise CannotConnect
+
+ result = await response.json()
+ monitors = result.get("monitors", {})
+
+ except aiohttp.ClientError as err:
+ _LOGGER.error("Error getting monitor status: %s", err)
+ raise CannotConnect from err
+
+ # Return info that you want to store in the config entry.
+ return {
+ "title": f"Hyprmonitors ({host}:{port})",
+ "host": host,
+ "port": port,
+ "monitor_count": len(monitors),
+ }
+
+
+class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
+ """Handle a config flow for Hyprland Monitor Control."""
+
+ VERSION = 1
+
+ async def async_step_user(
+ self, user_input: dict[str, Any] | None = None
+ ) -> FlowResult:
+ """Handle the initial step."""
+ errors: dict[str, str] = {}
+ if user_input is not None:
+ try:
+ info = await validate_input(self.hass, user_input)
+ except CannotConnect:
+ errors["base"] = "cannot_connect"
+ except InvalidAuth:
+ errors["base"] = "invalid_auth"
+ except Exception: # pylint: disable=broad-except
+ _LOGGER.exception("Unexpected exception")
+ errors["base"] = "unknown"
+ else:
+ # Create a unique ID based on host and port
+ unique_id = f"{user_input[CONF_HOST]}_{user_input[CONF_PORT]}"
+ await self.async_set_unique_id(unique_id)
+ self._abort_if_unique_id_configured()
+
+ return self.async_create_entry(
+ title=info["title"],
+ data={
+ CONF_HOST: info["host"],
+ CONF_PORT: info["port"],
+ },
+ )
+
+ return self.async_show_form(
+ step_id="user",
+ data_schema=STEP_USER_DATA_SCHEMA,
+ errors=errors,
+ description_placeholders={
+ "default_host": DEFAULT_HOST,
+ "default_port": str(DEFAULT_PORT),
+ },
+ )
+
+ async def async_step_import(self, import_info: dict[str, Any]) -> FlowResult:
+ """Handle import from configuration.yaml."""
+ return await self.async_step_user(import_info)
+
+
+class CannotConnect(HomeAssistantError):
+ """Error to indicate we cannot connect."""
+
+
+class InvalidAuth(HomeAssistantError):
+ """Error to indicate there is invalid auth."""
diff --git a/homeassistant/custom_components/hyprmonitors/const.py b/homeassistant/custom_components/hyprmonitors/const.py
new file mode 100644
index 0000000..fee018a
--- /dev/null
+++ b/homeassistant/custom_components/hyprmonitors/const.py
@@ -0,0 +1,32 @@
+"""Constants for the Hyprland Monitor Control integration."""
+from homeassistant.const import Platform
+
+DOMAIN = "hyprmonitors"
+
+# Platforms
+PLATFORMS = [Platform.SENSOR, Platform.SWITCH]
+
+# Default values
+DEFAULT_HOST = "localhost"
+DEFAULT_PORT = 3000
+DEFAULT_SCAN_INTERVAL = 30
+
+# Configuration keys
+CONF_HOST = "host"
+CONF_PORT = "port"
+
+# Attributes
+ATTR_MONITOR_NAME = "monitor_name"
+ATTR_RESOLUTION = "resolution"
+ATTR_REFRESH_RATE = "refresh_rate"
+ATTR_SCALE = "scale"
+ATTR_POSITION = "position"
+
+# Service names
+SERVICE_TURN_ON_MONITOR = "turn_on_monitor"
+SERVICE_TURN_OFF_MONITOR = "turn_off_monitor"
+SERVICE_TURN_ON_ALL = "turn_on_all_monitors"
+SERVICE_TURN_OFF_ALL = "turn_off_all_monitors"
+
+# Entity names
+ENTITY_ID_ALL_MONITORS = "switch.hyprmonitors_all_monitors"
diff --git a/homeassistant/custom_components/hyprmonitors/manifest.json b/homeassistant/custom_components/hyprmonitors/manifest.json
new file mode 100644
index 0000000..6d14799
--- /dev/null
+++ b/homeassistant/custom_components/hyprmonitors/manifest.json
@@ -0,0 +1,17 @@
+{
+ "domain": "hyprmonitors",
+ "name": "Hyprland Monitor Control",
+ "codeowners": ["@hyprmonitors"],
+ "config_flow": true,
+ "dependencies": [],
+ "documentation": "https://github.com/your-username/hyprmonitors",
+ "homeassistant": "2023.1.0",
+ "iot_class": "local_polling",
+ "issue_tracker": "https://github.com/your-username/hyprmonitors/issues",
+ "requirements": [
+ "aiohttp>=3.8.0"
+ ],
+ "ssdp": [],
+ "version": "1.0.0",
+ "zeroconf": []
+}
diff --git a/homeassistant/custom_components/hyprmonitors/sensor.py b/homeassistant/custom_components/hyprmonitors/sensor.py
new file mode 100644
index 0000000..77605a5
--- /dev/null
+++ b/homeassistant/custom_components/hyprmonitors/sensor.py
@@ -0,0 +1,207 @@
+"""Sensor platform for Hyprland Monitor Control integration."""
+from __future__ import annotations
+
+import logging
+from datetime import timedelta
+from typing import Any
+
+from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.core import HomeAssistant
+from homeassistant.helpers.entity import DeviceInfo
+from homeassistant.helpers.entity_platform import AddEntitiesCallback
+from homeassistant.helpers.update_coordinator import (
+ CoordinatorEntity,
+ DataUpdateCoordinator,
+ UpdateFailed,
+)
+
+from . import HyprmonitorsConfigEntry, HyprmonitorsData
+from .const import DEFAULT_SCAN_INTERVAL, DOMAIN
+
+_LOGGER = logging.getLogger(__name__)
+
+SCAN_INTERVAL = timedelta(seconds=DEFAULT_SCAN_INTERVAL)
+
+
+class HyprmonitorsDataUpdateCoordinator(DataUpdateCoordinator):
+ """Class to manage fetching data from the API."""
+
+ def __init__(
+ self,
+ hass: HomeAssistant,
+ *,
+ api: HyprmonitorsData,
+ ) -> None:
+ """Initialize."""
+ self.api = api
+ super().__init__(
+ hass,
+ _LOGGER,
+ name=DOMAIN,
+ update_interval=SCAN_INTERVAL,
+ )
+
+ async def _async_update_data(self) -> dict[str, Any]:
+ """Update data via library."""
+ try:
+ return await self.api.async_get_monitors()
+ except Exception as exception:
+ raise UpdateFailed(f"Error communicating with API: {exception}") from exception
+
+
+async def async_setup_entry(
+ hass: HomeAssistant,
+ config_entry: HyprmonitorsConfigEntry,
+ async_add_entities: AddEntitiesCallback,
+) -> None:
+ """Set up the sensor platform."""
+ api = config_entry.runtime_data
+
+ # Create coordinator
+ coordinator = HyprmonitorsDataUpdateCoordinator(hass, api=api)
+
+ # Fetch initial data so we have data when entities are added
+ await coordinator.async_config_entry_first_refresh()
+
+ # Create sensors for each monitor
+ entities = []
+
+ # Add overall status sensor
+ entities.append(
+ HyprmonitorsOverallSensor(
+ coordinator=coordinator,
+ config_entry=config_entry,
+ )
+ )
+
+ # Add individual monitor sensors
+ if coordinator.data:
+ for monitor_name in coordinator.data:
+ entities.append(
+ HyprmonitorsMonitorSensor(
+ coordinator=coordinator,
+ config_entry=config_entry,
+ monitor_name=monitor_name,
+ )
+ )
+
+ async_add_entities(entities)
+
+
+class HyprmonitorsBaseSensor(CoordinatorEntity, SensorEntity):
+ """Base class for Hyprmonitors sensors."""
+
+ def __init__(
+ self,
+ coordinator: HyprmonitorsDataUpdateCoordinator,
+ config_entry: ConfigEntry,
+ ) -> None:
+ """Initialize the sensor."""
+ super().__init__(coordinator)
+ self._config_entry = config_entry
+ self._attr_has_entity_name = True
+
+ @property
+ def device_info(self) -> DeviceInfo:
+ """Return device information."""
+ return DeviceInfo(
+ identifiers={(DOMAIN, self._config_entry.entry_id)},
+ name="Hyprland Monitors",
+ manufacturer="Hyprland",
+ model="Monitor Control",
+ sw_version="1.0.0",
+ configuration_url=f"http://{self._config_entry.data['host']}:{self._config_entry.data['port']}",
+ )
+
+
+class HyprmonitorsOverallSensor(HyprmonitorsBaseSensor):
+ """Sensor for overall monitor status."""
+
+ def __init__(
+ self,
+ coordinator: HyprmonitorsDataUpdateCoordinator,
+ config_entry: ConfigEntry,
+ ) -> None:
+ """Initialize the sensor."""
+ super().__init__(coordinator, config_entry)
+ self._attr_name = "Monitor Status"
+ self._attr_unique_id = f"{config_entry.entry_id}_overall_status"
+ self._attr_icon = "mdi:monitor-multiple"
+
+ @property
+ def native_value(self) -> str | None:
+ """Return the state of the sensor."""
+ if not self.coordinator.data:
+ return "unavailable"
+
+ monitors = self.coordinator.data
+ total_monitors = len(monitors)
+ on_monitors = sum(1 for status in monitors.values() if status == "on")
+
+ if on_monitors == 0:
+ return "all_off"
+ elif on_monitors == total_monitors:
+ return "all_on"
+ else:
+ return "mixed"
+
+ @property
+ def extra_state_attributes(self) -> dict[str, Any]:
+ """Return additional state attributes."""
+ if not self.coordinator.data:
+ return {}
+
+ monitors = self.coordinator.data
+ total_monitors = len(monitors)
+ on_monitors = sum(1 for status in monitors.values() if status == "on")
+ off_monitors = total_monitors - on_monitors
+
+ return {
+ "total_monitors": total_monitors,
+ "monitors_on": on_monitors,
+ "monitors_off": off_monitors,
+ "monitor_details": monitors,
+ }
+
+
+class HyprmonitorsMonitorSensor(HyprmonitorsBaseSensor):
+ """Sensor for individual monitor status."""
+
+ def __init__(
+ self,
+ coordinator: HyprmonitorsDataUpdateCoordinator,
+ config_entry: ConfigEntry,
+ monitor_name: str,
+ ) -> None:
+ """Initialize the sensor."""
+ super().__init__(coordinator, config_entry)
+ self._monitor_name = monitor_name
+ self._attr_name = f"{monitor_name} Status"
+ self._attr_unique_id = f"{config_entry.entry_id}_{monitor_name}_status"
+ self._attr_icon = "mdi:monitor"
+
+ @property
+ def native_value(self) -> str | None:
+ """Return the state of the sensor."""
+ if not self.coordinator.data:
+ return "unavailable"
+
+ return self.coordinator.data.get(self._monitor_name, "unknown")
+
+ @property
+ def extra_state_attributes(self) -> dict[str, Any]:
+ """Return additional state attributes."""
+ return {
+ "monitor_name": self._monitor_name,
+ "friendly_name": f"Monitor {self._monitor_name}",
+ }
+
+ @property
+ def available(self) -> bool:
+ """Return True if entity is available."""
+ return (
+ super().available
+ and self.coordinator.data is not None
+ and self._monitor_name in self.coordinator.data
+ )
diff --git a/homeassistant/custom_components/hyprmonitors/services.yaml b/homeassistant/custom_components/hyprmonitors/services.yaml
new file mode 100644
index 0000000..9c8918c
--- /dev/null
+++ b/homeassistant/custom_components/hyprmonitors/services.yaml
@@ -0,0 +1,29 @@
+turn_on_monitor:
+ name: Turn on monitor
+ description: Turn on a specific monitor by name
+ fields:
+ monitor:
+ name: Monitor name
+ description: Name of the monitor to turn on (e.g., DP-1, HDMI-A-1)
+ required: true
+ selector:
+ text:
+
+turn_off_monitor:
+ name: Turn off monitor
+ description: Turn off a specific monitor by name
+ fields:
+ monitor:
+ name: Monitor name
+ description: Name of the monitor to turn off (e.g., DP-1, HDMI-A-1)
+ required: true
+ selector:
+ text:
+
+turn_on_all_monitors:
+ name: Turn on all monitors
+ description: Turn on all monitors connected to Hyprland
+
+turn_off_all_monitors:
+ name: Turn off all monitors
+ description: Turn off all monitors connected to Hyprland
diff --git a/homeassistant/custom_components/hyprmonitors/strings.json b/homeassistant/custom_components/hyprmonitors/strings.json
new file mode 100644
index 0000000..400f6d6
--- /dev/null
+++ b/homeassistant/custom_components/hyprmonitors/strings.json
@@ -0,0 +1,88 @@
+{
+ "config": {
+ "step": {
+ "user": {
+ "title": "Hyprland Monitor Control",
+ "description": "Configure connection to your Hyprmonitors server",
+ "data": {
+ "host": "Host",
+ "port": "Port"
+ },
+ "data_description": {
+ "host": "IP address or hostname of the Hyprmonitors server (default: {default_host})",
+ "port": "Port number of the Hyprmonitors server (default: {default_port})"
+ }
+ }
+ },
+ "error": {
+ "cannot_connect": "Failed to connect to the Hyprmonitors server. Please check the host and port.",
+ "invalid_auth": "Authentication failed. Please check your credentials.",
+ "unknown": "Unexpected error occurred."
+ },
+ "abort": {
+ "already_configured": "This Hyprmonitors server is already configured."
+ }
+ },
+ "options": {
+ "step": {
+ "init": {
+ "title": "Hyprland Monitor Control Options",
+ "description": "Configure advanced options for the integration",
+ "data": {
+ "scan_interval": "Scan Interval (seconds)"
+ }
+ }
+ }
+ },
+ "entity": {
+ "sensor": {
+ "monitor_status": {
+ "name": "Monitor Status",
+ "state": {
+ "all_on": "All On",
+ "all_off": "All Off",
+ "mixed": "Mixed",
+ "unavailable": "Unavailable"
+ }
+ }
+ },
+ "switch": {
+ "all_monitors": {
+ "name": "All Monitors"
+ },
+ "monitor": {
+ "name": "Monitor {monitor_name}"
+ }
+ }
+ },
+ "services": {
+ "turn_on_monitor": {
+ "name": "Turn on monitor",
+ "description": "Turn on a specific monitor by name",
+ "fields": {
+ "monitor": {
+ "name": "Monitor name",
+ "description": "Name of the monitor to turn on (e.g., DP-1, HDMI-A-1)"
+ }
+ }
+ },
+ "turn_off_monitor": {
+ "name": "Turn off monitor",
+ "description": "Turn off a specific monitor by name",
+ "fields": {
+ "monitor": {
+ "name": "Monitor name",
+ "description": "Name of the monitor to turn off (e.g., DP-1, HDMI-A-1)"
+ }
+ }
+ },
+ "turn_on_all_monitors": {
+ "name": "Turn on all monitors",
+ "description": "Turn on all monitors connected to Hyprland"
+ },
+ "turn_off_all_monitors": {
+ "name": "Turn off all monitors",
+ "description": "Turn off all monitors connected to Hyprland"
+ }
+ }
+}
diff --git a/homeassistant/custom_components/hyprmonitors/switch.py b/homeassistant/custom_components/hyprmonitors/switch.py
new file mode 100644
index 0000000..191bde6
--- /dev/null
+++ b/homeassistant/custom_components/hyprmonitors/switch.py
@@ -0,0 +1,218 @@
+"""Switch platform for Hyprland Monitor Control integration."""
+from __future__ import annotations
+
+import logging
+from typing import Any
+
+from homeassistant.components.switch import SwitchEntity
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.core import HomeAssistant
+from homeassistant.helpers.entity import DeviceInfo
+from homeassistant.helpers.entity_platform import AddEntitiesCallback
+from homeassistant.helpers.update_coordinator import CoordinatorEntity
+
+from . import HyprmonitorsConfigEntry, HyprmonitorsData
+from .const import DOMAIN
+from .sensor import HyprmonitorsDataUpdateCoordinator
+
+_LOGGER = logging.getLogger(__name__)
+
+
+async def async_setup_entry(
+ hass: HomeAssistant,
+ config_entry: HyprmonitorsConfigEntry,
+ async_add_entities: AddEntitiesCallback,
+) -> None:
+ """Set up the switch platform."""
+ api = config_entry.runtime_data
+
+ # Get coordinator from sensor platform
+ coordinator = None
+ for entry in hass.config_entries.async_entries(DOMAIN):
+ if entry.entry_id == config_entry.entry_id:
+ # Create coordinator if it doesn't exist
+ coordinator = HyprmonitorsDataUpdateCoordinator(hass, api=api)
+ await coordinator.async_config_entry_first_refresh()
+ break
+
+ if coordinator is None:
+ _LOGGER.error("Could not find coordinator for config entry")
+ return
+
+ entities = []
+
+ # Add master switch for all monitors
+ entities.append(
+ HyprmonitorsAllMonitorsSwitch(
+ coordinator=coordinator,
+ config_entry=config_entry,
+ api=api,
+ )
+ )
+
+ # Add individual monitor switches
+ if coordinator.data:
+ for monitor_name in coordinator.data:
+ entities.append(
+ HyprmonitorsMonitorSwitch(
+ coordinator=coordinator,
+ config_entry=config_entry,
+ api=api,
+ monitor_name=monitor_name,
+ )
+ )
+
+ async_add_entities(entities)
+
+
+class HyprmonitorsBaseSwitch(CoordinatorEntity, SwitchEntity):
+ """Base class for Hyprmonitors switches."""
+
+ def __init__(
+ self,
+ coordinator: HyprmonitorsDataUpdateCoordinator,
+ config_entry: ConfigEntry,
+ api: HyprmonitorsData,
+ ) -> None:
+ """Initialize the switch."""
+ super().__init__(coordinator)
+ self._api = api
+ self._config_entry = config_entry
+ self._attr_has_entity_name = True
+
+ @property
+ def device_info(self) -> DeviceInfo:
+ """Return device information."""
+ return DeviceInfo(
+ identifiers={(DOMAIN, self._config_entry.entry_id)},
+ name="Hyprland Monitors",
+ manufacturer="Hyprland",
+ model="Monitor Control",
+ sw_version="1.0.0",
+ configuration_url=f"http://{self._config_entry.data['host']}:{self._config_entry.data['port']}",
+ )
+
+
+class HyprmonitorsAllMonitorsSwitch(HyprmonitorsBaseSwitch):
+ """Switch to control all monitors at once."""
+
+ def __init__(
+ self,
+ coordinator: HyprmonitorsDataUpdateCoordinator,
+ config_entry: ConfigEntry,
+ api: HyprmonitorsData,
+ ) -> None:
+ """Initialize the switch."""
+ super().__init__(coordinator, config_entry, api)
+ self._attr_name = "All Monitors"
+ self._attr_unique_id = f"{config_entry.entry_id}_all_monitors"
+ self._attr_icon = "mdi:monitor-multiple"
+
+ @property
+ def is_on(self) -> bool | None:
+ """Return true if all monitors are on."""
+ if not self.coordinator.data:
+ return None
+
+ monitors = self.coordinator.data
+ if not monitors:
+ return None
+
+ # Return True only if ALL monitors are on
+ return all(status == "on" for status in monitors.values())
+
+ @property
+ def extra_state_attributes(self) -> dict[str, Any]:
+ """Return additional state attributes."""
+ if not self.coordinator.data:
+ return {}
+
+ monitors = self.coordinator.data
+ total_monitors = len(monitors)
+ on_monitors = sum(1 for status in monitors.values() if status == "on")
+
+ return {
+ "total_monitors": total_monitors,
+ "monitors_on": on_monitors,
+ "monitors_off": total_monitors - on_monitors,
+ "monitor_names": list(monitors.keys()),
+ }
+
+ async def async_turn_on(self, **kwargs: Any) -> None:
+ """Turn all monitors on."""
+ success = await self._api.async_turn_all_monitors_on()
+ if success:
+ await self.coordinator.async_request_refresh()
+ else:
+ _LOGGER.error("Failed to turn on all monitors")
+
+ async def async_turn_off(self, **kwargs: Any) -> None:
+ """Turn all monitors off."""
+ success = await self._api.async_turn_all_monitors_off()
+ if success:
+ await self.coordinator.async_request_refresh()
+ else:
+ _LOGGER.error("Failed to turn off all monitors")
+
+
+class HyprmonitorsMonitorSwitch(HyprmonitorsBaseSwitch):
+ """Switch to control individual monitors."""
+
+ def __init__(
+ self,
+ coordinator: HyprmonitorsDataUpdateCoordinator,
+ config_entry: ConfigEntry,
+ api: HyprmonitorsData,
+ monitor_name: str,
+ ) -> None:
+ """Initialize the switch."""
+ super().__init__(coordinator, config_entry, api)
+ self._monitor_name = monitor_name
+ self._attr_name = f"Monitor {monitor_name}"
+ self._attr_unique_id = f"{config_entry.entry_id}_{monitor_name}"
+ self._attr_icon = "mdi:monitor"
+
+ @property
+ def is_on(self) -> bool | None:
+ """Return true if the monitor is on."""
+ if not self.coordinator.data:
+ return None
+
+ status = self.coordinator.data.get(self._monitor_name)
+ if status is None:
+ return None
+
+ return status == "on"
+
+ @property
+ def extra_state_attributes(self) -> dict[str, Any]:
+ """Return additional state attributes."""
+ return {
+ "monitor_name": self._monitor_name,
+ "friendly_name": f"Monitor {self._monitor_name}",
+ }
+
+ @property
+ def available(self) -> bool:
+ """Return True if entity is available."""
+ return (
+ super().available
+ and self.coordinator.data is not None
+ and self._monitor_name in self.coordinator.data
+ )
+
+ async def async_turn_on(self, **kwargs: Any) -> None:
+ """Turn the monitor on."""
+ success = await self._api.async_turn_monitor_on(self._monitor_name)
+ if success:
+ await self.coordinator.async_request_refresh()
+ else:
+ _LOGGER.error("Failed to turn on monitor %s", self._monitor_name)
+
+ async def async_turn_off(self, **kwargs: Any) -> None:
+ """Turn the monitor off."""
+ success = await self._api.async_turn_monitor_off(self._monitor_name)
+ if success:
+ await self.coordinator.async_request_refresh()
+ else:
+ _LOGGER.error("Failed to turn off monitor %s", self._monitor_name)
diff --git a/homeassistant/docker-compose.example.yml b/homeassistant/docker-compose.example.yml
new file mode 100644
index 0000000..8eed8fb
--- /dev/null
+++ b/homeassistant/docker-compose.example.yml
@@ -0,0 +1,80 @@
+version: '3.8'
+
+services:
+ homeassistant:
+ container_name: homeassistant
+ image: ghcr.io/home-assistant/home-assistant:stable
+ volumes:
+ # Main Home Assistant configuration
+ - ./homeassistant-config:/config
+
+ # Mount the Hyprmonitors integration directly
+ - ./custom_components/hyprmonitors:/config/custom_components/hyprmonitors:ro
+
+ # Optional: Mount examples for reference
+ - ./examples.yaml:/config/hyprmonitors-examples.yaml:ro
+
+ # System access (needed for some integrations)
+ - /etc/localtime:/etc/localtime:ro
+
+ restart: unless-stopped
+ privileged: true
+ network_mode: host
+
+ environment:
+ # Set timezone
+ - TZ=America/New_York # Change to your timezone
+
+ # Health check
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:8123/api/"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 30s
+
+ # Optional: Include the Hyprmonitors server in the same compose file
+ hyprmonitors:
+ container_name: hyprmonitors-server
+ build:
+ context: ../
+ dockerfile: Dockerfile
+ ports:
+ - "3000:3000"
+ restart: unless-stopped
+ environment:
+ - HYPRMONITORS_HOST=0.0.0.0
+ - HYPRMONITORS_PORT=3000
+ - RUST_LOG=info
+
+ # Mount Hyprland socket (adjust path as needed)
+ volumes:
+ - /tmp/hypr:/tmp/hypr:ro
+ - $XDG_RUNTIME_DIR/hypr:/run/user/1000/hypr:ro
+
+ # Network access to Hyprland
+ network_mode: host
+
+ # Ensure Hyprland environment is available
+ environment:
+ - XDG_RUNTIME_DIR=/run/user/1000
+ - HYPRLAND_INSTANCE_SIGNATURE=${HYPRLAND_INSTANCE_SIGNATURE}
+
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 10s
+
+# Optional: Create a custom network if not using host networking
+networks:
+ homelab:
+ driver: bridge
+ ipam:
+ config:
+ - subnet: 172.20.0.0/16
+
+volumes:
+ homeassistant-config:
+ driver: local
diff --git a/homeassistant/examples.yaml b/homeassistant/examples.yaml
new file mode 100644
index 0000000..18d215b
--- /dev/null
+++ b/homeassistant/examples.yaml
@@ -0,0 +1,330 @@
+# Sample Home Assistant configuration for Hyprland Monitor Control
+# Copy relevant sections to your configuration files
+
+# Automations
+automation:
+ # Basic presence-based control
+ - alias: "Monitors - Turn off when away"
+ description: "Turn off all monitors when nobody is home"
+ trigger:
+ - platform: state
+ entity_id: group.all_persons
+ to: "not_home"
+ for: "00:05:00" # Wait 5 minutes to avoid false triggers
+ action:
+ - service: hyprmonitors.turn_off_all_monitors
+ - service: notify.persistent_notification
+ data:
+ title: "Monitor Control"
+ message: "All monitors turned off - nobody home"
+
+ - alias: "Monitors - Turn on when arriving home"
+ description: "Turn on monitors when someone arrives home"
+ trigger:
+ - platform: state
+ entity_id: group.all_persons
+ to: "home"
+ condition:
+ - condition: time
+ after: "07:00:00"
+ before: "23:00:00"
+ action:
+ - service: hyprmonitors.turn_on_all_monitors
+ - service: notify.persistent_notification
+ data:
+ title: "Monitor Control"
+ message: "Monitors turned on - welcome home!"
+
+ # Time-based control
+ - alias: "Monitors - Morning startup"
+ description: "Turn on monitors in the morning on weekdays"
+ trigger:
+ - platform: time
+ at: "07:30:00"
+ condition:
+ - condition: time
+ weekday:
+ - mon
+ - tue
+ - wed
+ - thu
+ - fri
+ - condition: state
+ entity_id: group.all_persons
+ state: "home"
+ action:
+ - service: switch.turn_on
+ entity_id: switch.hyprmonitors_all_monitors
+
+ - alias: "Monitors - Evening shutdown"
+ description: "Turn off monitors late at night"
+ trigger:
+ - platform: time
+ at: "23:30:00"
+ action:
+ - service: switch.turn_off
+ entity_id: switch.hyprmonitors_all_monitors
+
+ # Work mode automation
+ - alias: "Monitors - Focus mode"
+ description: "Turn off secondary monitors during focus time"
+ trigger:
+ - platform: state
+ entity_id: input_boolean.focus_mode
+ to: "on"
+ action:
+ - service: hyprmonitors.turn_off_monitor
+ data:
+ monitor: "DP-2" # Adjust to your secondary monitor
+ - service: hyprmonitors.turn_off_monitor
+ data:
+ monitor: "HDMI-A-1" # Adjust as needed
+
+ - alias: "Monitors - Exit focus mode"
+ description: "Turn on all monitors when exiting focus mode"
+ trigger:
+ - platform: state
+ entity_id: input_boolean.focus_mode
+ to: "off"
+ action:
+ - service: hyprmonitors.turn_on_all_monitors
+
+ # Meeting automation (requires calendar integration)
+ - alias: "Monitors - Meeting mode"
+ description: "Adjust monitors for meetings"
+ trigger:
+ - platform: calendar
+ event: start
+ entity_id: calendar.work
+ condition:
+ - condition: template
+ value_template: >
+ {{ 'meeting' in trigger.calendar_event.summary.lower() or
+ 'call' in trigger.calendar_event.summary.lower() }}
+ action:
+ # Keep only main monitor on for meetings
+ - service: hyprmonitors.turn_on_monitor
+ data:
+ monitor: "DP-1" # Main monitor
+ - service: hyprmonitors.turn_off_monitor
+ data:
+ monitor: "DP-2" # Secondary monitor
+
+ # Power saving based on computer usage
+ - alias: "Monitors - Idle detection"
+ description: "Turn off monitors when computer is idle"
+ trigger:
+ - platform: state
+ entity_id: binary_sensor.computer_active # You'd need to create this
+ to: "off"
+ for: "00:10:00" # 10 minutes idle
+ condition:
+ - condition: time
+ after: "09:00:00"
+ before: "22:00:00"
+ action:
+ - service: hyprmonitors.turn_off_all_monitors
+
+ - alias: "Monitors - Activity detected"
+ description: "Turn on monitors when computer becomes active"
+ trigger:
+ - platform: state
+ entity_id: binary_sensor.computer_active
+ to: "on"
+ condition:
+ - condition: state
+ entity_id: group.all_persons
+ state: "home"
+ action:
+ - service: hyprmonitors.turn_on_all_monitors
+
+# Input Booleans for manual control
+input_boolean:
+ focus_mode:
+ name: "Focus Mode"
+ icon: mdi:focus-field
+
+ monitor_automation:
+ name: "Monitor Automation Enabled"
+ initial: true
+ icon: mdi:auto-mode
+
+# Scripts for complex monitor control
+script:
+ gaming_mode:
+ alias: "Gaming Mode"
+ description: "Optimize monitors for gaming"
+ sequence:
+ - service: hyprmonitors.turn_on_monitor
+ data:
+ monitor: "DP-1" # Main gaming monitor
+ - service: hyprmonitors.turn_off_monitor
+ data:
+ monitor: "DP-2" # Turn off secondary to reduce distractions
+ - service: notify.persistent_notification
+ data:
+ title: "Monitor Control"
+ message: "Gaming mode activated"
+
+ work_mode:
+ alias: "Work Mode"
+ description: "Setup monitors for work"
+ sequence:
+ - service: hyprmonitors.turn_on_all_monitors
+ - delay: "00:00:02" # Small delay between commands
+ - service: notify.persistent_notification
+ data:
+ title: "Monitor Control"
+ message: "Work mode activated - all monitors on"
+
+ presentation_mode:
+ alias: "Presentation Mode"
+ description: "Setup for presentations"
+ sequence:
+ - service: hyprmonitors.turn_on_monitor
+ data:
+ monitor: "HDMI-A-1" # Projector/external display
+ - service: hyprmonitors.turn_on_monitor
+ data:
+ monitor: "DP-1" # Laptop screen for notes
+ - service: notify.persistent_notification
+ data:
+ title: "Monitor Control"
+ message: "Presentation mode ready"
+
+# Sensors for monitoring
+sensor:
+ - platform: template
+ sensors:
+ monitor_power_usage:
+ friendly_name: "Estimated Monitor Power Usage"
+ unit_of_measurement: "W"
+ value_template: >
+ {% set total_monitors = state_attr('sensor.hyprmonitors_monitor_status', 'total_monitors') | int %}
+ {% set monitors_on = state_attr('sensor.hyprmonitors_monitor_status', 'monitors_on') | int %}
+ {{ monitors_on * 30 }} # Assuming 30W per monitor
+ icon_template: mdi:lightning-bolt
+
+# Groups for organization
+group:
+ monitor_controls:
+ name: "Monitor Controls"
+ entities:
+ - switch.hyprmonitors_all_monitors
+ - sensor.hyprmonitors_monitor_status
+ - input_boolean.focus_mode
+ - input_boolean.monitor_automation
+
+# Lovelace Dashboard Configuration
+# Add this to your dashboard YAML or use the UI editor
+
+# Main Monitor Control Card
+type: vertical-stack
+cards:
+ - type: entities
+ title: "Monitor Status"
+ entities:
+ - entity: sensor.hyprmonitors_monitor_status
+ name: "Overall Status"
+ icon: mdi:monitor-multiple
+ - entity: sensor.monitor_power_usage
+ name: "Est. Power Usage"
+ - type: divider
+ - entity: switch.hyprmonitors_all_monitors
+ name: "All Monitors"
+ icon: mdi:power
+
+ - type: horizontal-stack
+ cards:
+ - type: button
+ entity: script.work_mode
+ name: "Work Mode"
+ icon: mdi:briefcase
+ tap_action:
+ action: call-service
+ service: script.work_mode
+
+ - type: button
+ entity: script.gaming_mode
+ name: "Gaming"
+ icon: mdi:gamepad-variant
+ tap_action:
+ action: call-service
+ service: script.gaming_mode
+
+ - type: button
+ entity: script.presentation_mode
+ name: "Present"
+ icon: mdi:presentation
+ tap_action:
+ action: call-service
+ service: script.presentation_mode
+
+ - type: entities
+ title: "Individual Monitors"
+ entities:
+ - entity: switch.hyprmonitors_dp_1
+ name: "Main Monitor (DP-1)"
+ icon: mdi:monitor
+ - entity: switch.hyprmonitors_dp_2
+ name: "Secondary (DP-2)"
+ icon: mdi:monitor
+ - entity: switch.hyprmonitors_hdmi_a_1
+ name: "External (HDMI)"
+ icon: mdi:monitor-speaker
+
+ - type: entities
+ title: "Automation Settings"
+ entities:
+ - entity: input_boolean.monitor_automation
+ name: "Enable Automation"
+ - entity: input_boolean.focus_mode
+ name: "Focus Mode"
+
+# Alternative Glance Card Layout
+type: glance
+entities:
+ - entity: sensor.hyprmonitors_monitor_status
+ name: "Status"
+ - entity: switch.hyprmonitors_all_monitors
+ name: "All"
+ - entity: switch.hyprmonitors_dp_1
+ name: "Main"
+ - entity: switch.hyprmonitors_dp_2
+ name: "Secondary"
+title: "Monitor Quick Control"
+columns: 4
+
+# Advanced Card with Custom Button Row
+type: custom:button-card
+name: "Monitor Control Hub"
+styles:
+ card:
+ - height: 120px
+tap_action:
+ action: more-info
+ entity: sensor.hyprmonitors_monitor_status
+custom_fields:
+ status: |
+ [[[
+ const status = states['sensor.hyprmonitors_monitor_status'].state;
+ const icon = status === 'all_on' ? 'mdi:monitor-multiple' :
+ status === 'all_off' ? 'mdi:monitor-off' : 'mdi:monitor-shimmer';
+ return `