feat: Added homeassistant plugin
This commit is contained in:
315
homeassistant/README.md
Normal file
315
homeassistant/README.md
Normal file
@@ -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.
|
||||
206
homeassistant/custom_components/hyprmonitors/__init__.py
Normal file
206
homeassistant/custom_components/hyprmonitors/__init__.py
Normal file
@@ -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,
|
||||
)
|
||||
134
homeassistant/custom_components/hyprmonitors/config_flow.py
Normal file
134
homeassistant/custom_components/hyprmonitors/config_flow.py
Normal file
@@ -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."""
|
||||
32
homeassistant/custom_components/hyprmonitors/const.py
Normal file
32
homeassistant/custom_components/hyprmonitors/const.py
Normal file
@@ -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"
|
||||
17
homeassistant/custom_components/hyprmonitors/manifest.json
Normal file
17
homeassistant/custom_components/hyprmonitors/manifest.json
Normal file
@@ -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": []
|
||||
}
|
||||
207
homeassistant/custom_components/hyprmonitors/sensor.py
Normal file
207
homeassistant/custom_components/hyprmonitors/sensor.py
Normal file
@@ -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
|
||||
)
|
||||
29
homeassistant/custom_components/hyprmonitors/services.yaml
Normal file
29
homeassistant/custom_components/hyprmonitors/services.yaml
Normal file
@@ -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
|
||||
88
homeassistant/custom_components/hyprmonitors/strings.json
Normal file
88
homeassistant/custom_components/hyprmonitors/strings.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
218
homeassistant/custom_components/hyprmonitors/switch.py
Normal file
218
homeassistant/custom_components/hyprmonitors/switch.py
Normal file
@@ -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)
|
||||
80
homeassistant/docker-compose.example.yml
Normal file
80
homeassistant/docker-compose.example.yml
Normal file
@@ -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
|
||||
330
homeassistant/examples.yaml
Normal file
330
homeassistant/examples.yaml
Normal file
@@ -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 `<ha-icon icon="${icon}" style="color: var(--primary-color); font-size: 24px;"></ha-icon>`;
|
||||
]]]
|
||||
buttons: |
|
||||
[[[
|
||||
return `
|
||||
<div style="display: flex; gap: 8px; margin-top: 10px;">
|
||||
<mwc-button dense outlined onclick="hass.callService('script', 'turn_on', {entity_id: 'script.work_mode'})">
|
||||
Work
|
||||
</mwc-button>
|
||||
<mwc-button dense outlined onclick="hass.callService('script', 'turn_on', {entity_id: 'script.gaming_mode'})">
|
||||
Game
|
||||
</mwc-button>
|
||||
<mwc-button dense outlined onclick="hass.callService('hyprmonitors', 'turn_off_all_monitors')">
|
||||
Off
|
||||
</mwc-button>
|
||||
</div>
|
||||
`;
|
||||
]]]
|
||||
316
homeassistant/install.sh
Executable file
316
homeassistant/install.sh
Executable file
@@ -0,0 +1,316 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Hyprland Monitor Control - Home Assistant Integration Installer
|
||||
# This script helps install the custom integration into Home Assistant
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Default paths
|
||||
DEFAULT_HA_CONFIG="$HOME/.homeassistant"
|
||||
DEFAULT_HA_CONFIG_ALT="/config"
|
||||
INTEGRATION_NAME="hyprmonitors"
|
||||
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
print_header() {
|
||||
echo -e "${BLUE}================================================${NC}"
|
||||
echo -e "${BLUE} Hyprland Monitor Control - HA Integration${NC}"
|
||||
echo -e "${BLUE}================================================${NC}"
|
||||
echo
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}✓ $1${NC}"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}⚠ $1${NC}"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}✗ $1${NC}"
|
||||
}
|
||||
|
||||
print_info() {
|
||||
echo -e "${BLUE}ℹ $1${NC}"
|
||||
}
|
||||
|
||||
detect_ha_config() {
|
||||
local config_dir=""
|
||||
|
||||
# Try common Home Assistant config directories
|
||||
if [[ -d "$DEFAULT_HA_CONFIG" ]]; then
|
||||
config_dir="$DEFAULT_HA_CONFIG"
|
||||
elif [[ -d "$DEFAULT_HA_CONFIG_ALT" ]]; then
|
||||
config_dir="$DEFAULT_HA_CONFIG_ALT"
|
||||
elif [[ -d "/usr/share/hassio/homeassistant" ]]; then
|
||||
config_dir="/usr/share/hassio/homeassistant"
|
||||
elif [[ -d "/data" ]]; then
|
||||
config_dir="/data"
|
||||
fi
|
||||
|
||||
echo "$config_dir"
|
||||
}
|
||||
|
||||
check_ha_running() {
|
||||
if command -v systemctl &> /dev/null; then
|
||||
if systemctl is-active --quiet home-assistant || systemctl is-active --quiet homeassistant; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for common HA processes
|
||||
if pgrep -f "homeassistant" &> /dev/null || pgrep -f "hass" &> /dev/null; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
create_backup() {
|
||||
local target_dir="$1"
|
||||
local backup_dir="${target_dir}.backup.$(date +%Y%m%d_%H%M%S)"
|
||||
|
||||
if [[ -d "$target_dir" ]]; then
|
||||
print_info "Creating backup of existing integration..."
|
||||
cp -r "$target_dir" "$backup_dir"
|
||||
print_success "Backup created: $backup_dir"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
install_integration() {
|
||||
local ha_config="$1"
|
||||
local custom_components_dir="$ha_config/custom_components"
|
||||
local target_dir="$custom_components_dir/$INTEGRATION_NAME"
|
||||
local source_components_dir="$SOURCE_DIR/custom_components/$INTEGRATION_NAME"
|
||||
|
||||
# Verify source directory exists
|
||||
if [[ ! -d "$source_components_dir" ]]; then
|
||||
print_error "Source integration directory not found: $source_components_dir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create custom_components directory if it doesn't exist
|
||||
mkdir -p "$custom_components_dir"
|
||||
print_success "Custom components directory ready: $custom_components_dir"
|
||||
|
||||
# Create backup if integration already exists
|
||||
create_backup "$target_dir"
|
||||
|
||||
# Copy integration files
|
||||
print_info "Installing Hyprland Monitor Control integration..."
|
||||
cp -r "$source_components_dir" "$target_dir"
|
||||
|
||||
# Set appropriate permissions
|
||||
chmod -R 755 "$target_dir"
|
||||
|
||||
print_success "Integration installed successfully!"
|
||||
print_info "Installation location: $target_dir"
|
||||
|
||||
# List installed files
|
||||
echo
|
||||
print_info "Installed files:"
|
||||
find "$target_dir" -type f -name "*.py" -o -name "*.json" -o -name "*.yaml" | sort | while read -r file; do
|
||||
echo " - ${file#$target_dir/}"
|
||||
done
|
||||
}
|
||||
|
||||
verify_installation() {
|
||||
local ha_config="$1"
|
||||
local target_dir="$ha_config/custom_components/$INTEGRATION_NAME"
|
||||
|
||||
# Check required files
|
||||
local required_files=(
|
||||
"__init__.py"
|
||||
"manifest.json"
|
||||
"config_flow.py"
|
||||
"const.py"
|
||||
"sensor.py"
|
||||
"switch.py"
|
||||
"strings.json"
|
||||
"services.yaml"
|
||||
)
|
||||
|
||||
print_info "Verifying installation..."
|
||||
|
||||
for file in "${required_files[@]}"; do
|
||||
if [[ -f "$target_dir/$file" ]]; then
|
||||
print_success "Found: $file"
|
||||
else
|
||||
print_error "Missing: $file"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Check manifest.json structure
|
||||
if command -v python3 &> /dev/null; then
|
||||
if python3 -c "import json; json.load(open('$target_dir/manifest.json'))" 2>/dev/null; then
|
||||
print_success "manifest.json is valid JSON"
|
||||
else
|
||||
print_warning "manifest.json may have syntax errors"
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
check_hyprmonitors_server() {
|
||||
local host="${1:-localhost}"
|
||||
local port="${2:-3000}"
|
||||
|
||||
print_info "Checking Hyprmonitors server connection..."
|
||||
|
||||
if command -v curl &> /dev/null; then
|
||||
if curl -s --max-time 5 "http://$host:$port/health" > /dev/null 2>&1; then
|
||||
print_success "Hyprmonitors server is reachable at $host:$port"
|
||||
return 0
|
||||
else
|
||||
print_warning "Hyprmonitors server not reachable at $host:$port"
|
||||
print_info "Make sure the Rust server is running: cargo run"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
print_warning "curl not available, cannot test server connection"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
show_next_steps() {
|
||||
echo
|
||||
print_info "Installation complete! Next steps:"
|
||||
echo
|
||||
echo "1. Restart Home Assistant"
|
||||
echo " - If using systemctl: sudo systemctl restart home-assistant"
|
||||
echo " - If using Docker: docker restart homeassistant"
|
||||
echo " - If using HAOS: Settings → System → Restart"
|
||||
echo
|
||||
echo "2. Add the integration:"
|
||||
echo " - Go to Settings → Devices & Services"
|
||||
echo " - Click 'Add Integration'"
|
||||
echo " - Search for 'Hyprland Monitor Control'"
|
||||
echo " - Configure with your server details"
|
||||
echo
|
||||
echo "3. Make sure your Hyprmonitors server is running:"
|
||||
echo " - cd $(dirname "$SOURCE_DIR")"
|
||||
echo " - cargo run"
|
||||
echo
|
||||
echo "4. Optional: Check the examples.yaml file for automation ideas"
|
||||
echo
|
||||
print_success "Enjoy controlling your monitors from Home Assistant! 🖥️"
|
||||
}
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo
|
||||
echo "Options:"
|
||||
echo " -c, --config-dir DIR Home Assistant configuration directory"
|
||||
echo " -h, --help Show this help message"
|
||||
echo " --check-server HOST:PORT Check if Hyprmonitors server is running"
|
||||
echo " --dry-run Show what would be done without making changes"
|
||||
echo
|
||||
echo "Examples:"
|
||||
echo " $0 # Auto-detect HA config directory"
|
||||
echo " $0 -c /config # Specify config directory"
|
||||
echo " $0 --check-server localhost:3000 # Test server connection"
|
||||
echo " $0 --dry-run # Preview installation"
|
||||
}
|
||||
|
||||
main() {
|
||||
local ha_config=""
|
||||
local check_server=""
|
||||
local dry_run=false
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-c|--config-dir)
|
||||
ha_config="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
--check-server)
|
||||
check_server="$2"
|
||||
shift 2
|
||||
;;
|
||||
--dry-run)
|
||||
dry_run=true
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
print_error "Unknown option: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
print_header
|
||||
|
||||
# Handle server check
|
||||
if [[ -n "$check_server" ]]; then
|
||||
IFS=':' read -r host port <<< "$check_server"
|
||||
check_hyprmonitors_server "$host" "$port"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
# Detect Home Assistant config directory
|
||||
if [[ -z "$ha_config" ]]; then
|
||||
ha_config=$(detect_ha_config)
|
||||
if [[ -z "$ha_config" ]]; then
|
||||
print_error "Could not find Home Assistant configuration directory"
|
||||
print_info "Please specify it with: $0 -c /path/to/homeassistant/config"
|
||||
exit 1
|
||||
fi
|
||||
print_info "Auto-detected HA config: $ha_config"
|
||||
fi
|
||||
|
||||
# Verify config directory exists
|
||||
if [[ ! -d "$ha_config" ]]; then
|
||||
print_error "Home Assistant config directory not found: $ha_config"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if HA is running
|
||||
if check_ha_running; then
|
||||
print_warning "Home Assistant appears to be running"
|
||||
print_info "You'll need to restart it after installation"
|
||||
fi
|
||||
|
||||
if [[ "$dry_run" == true ]]; then
|
||||
print_info "DRY RUN - Would install integration to:"
|
||||
print_info " Source: $SOURCE_DIR/custom_components/$INTEGRATION_NAME"
|
||||
print_info " Target: $ha_config/custom_components/$INTEGRATION_NAME"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Install the integration
|
||||
install_integration "$ha_config"
|
||||
|
||||
# Verify installation
|
||||
if verify_installation "$ha_config"; then
|
||||
print_success "Installation verification passed!"
|
||||
else
|
||||
print_error "Installation verification failed!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check server connection
|
||||
check_hyprmonitors_server
|
||||
|
||||
# Show next steps
|
||||
show_next_steps
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user