feat: Added stuff
Some checks failed
build / checks-matrix (push) Failing after 19m1s
build / checks-build (push) Has been skipped
build / codecov (push) Failing after 19m2s
docs / docs (push) Has been cancelled

This commit is contained in:
uttarayan21
2025-08-16 20:35:09 +05:30
parent 0e3079a6f8
commit 8252d6c8b6
8 changed files with 653 additions and 13 deletions

View File

@@ -64,7 +64,7 @@
meta = with pkgs.lib; { meta = with pkgs.lib; {
description = "Hyprland monitor control server"; description = "Hyprland monitor control server";
homepage = "https://github.com/your-username/hyprmonitors"; homepage = "https://git.darksailor.dev/servius/hyprmonitors";
license = licenses.mit; license = licenses.mit;
maintainers = []; maintainers = [];
}; };

View File

@@ -0,0 +1,301 @@
# Docker Troubleshooting Guide for Hyprmonitors Home Assistant Integration
This guide helps resolve common networking issues when running Home Assistant in Docker containers and trying to connect to the Hyprmonitors daemon running on the host system.
## Quick Fix Summary
**TL;DR**: Replace `localhost` with `host.docker.internal` when configuring the integration in Home Assistant.
## The Problem
When Home Assistant runs inside a Docker container, `localhost` refers to the container's internal network, not the host system where your Hyprmonitors daemon is running. This causes connection timeouts and failures.
## Solutions
### Solution 1: Use Docker Host Alias (Recommended)
**For Docker Desktop (Windows/Mac):**
```
Host: host.docker.internal
Port: 3000
```
**For Docker on Linux:**
Add to your `docker-compose.yml`:
```yaml
services:
homeassistant:
# ... other config ...
extra_hosts:
- "host.docker.internal:host-gateway"
```
Or add to `docker run` command:
```bash
docker run --add-host=host.docker.internal:host-gateway ...
```
### Solution 2: Use Host Machine's IP Address
Find your host machine's IP address:
```bash
# Linux/Mac
hostname -I | awk '{print $1}'
# or
ip route get 8.8.8.8 | awk '{print $7}'
# Windows
ipconfig | findstr IPv4
```
Then use this IP in Home Assistant:
```
Host: 192.168.1.100 # Your actual IP
Port: 3000
```
### Solution 3: Host Network Mode
Run Home Assistant with host networking (Linux only):
```bash
docker run --network host ...
```
Or in `docker-compose.yml`:
```yaml
services:
homeassistant:
network_mode: host
# Remove ports section when using host mode
```
## Testing Connectivity
### Step 1: Run the Connectivity Test
From the `hyprmonitors/homeassistant` directory:
```bash
# Test default localhost (will likely fail in Docker)
python3 test_connectivity.py
# Test Docker host alias
python3 test_connectivity.py host.docker.internal 3000
# Test specific IP
python3 test_connectivity.py 192.168.1.100 3000
```
### Step 2: Test from Inside Home Assistant Container
```bash
# Get into the HA container
docker exec -it homeassistant bash
# Test connectivity
curl http://host.docker.internal:3000/health
# or
curl http://192.168.1.100:3000/health
```
Expected response:
```json
{"success":true,"message":"Hyprland Monitor Control Server is running","monitor":null}
```
## Common Docker Configurations
### Docker Compose Example
```yaml
version: '3.8'
services:
homeassistant:
container_name: homeassistant
image: ghcr.io/home-assistant/home-assistant:stable
volumes:
- ./config:/config
- /etc/localtime:/etc/localtime:ro
environment:
- TZ=America/New_York
ports:
- "8123:8123"
restart: unless-stopped
extra_hosts:
- "host.docker.internal:host-gateway" # Linux
# For Windows/Mac, this is automatic
```
### Docker Run Example
```bash
docker run -d \
--name homeassistant \
--privileged \
--restart=unless-stopped \
-e TZ=America/New_York \
-v /home/user/homeassistant:/config \
-v /etc/localtime:/etc/localtime:ro \
--add-host=host.docker.internal:host-gateway \
-p 8123:8123 \
ghcr.io/home-assistant/home-assistant:stable
```
## Firewall Configuration
Make sure port 3000 is accessible from Docker containers:
### UFW (Ubuntu)
```bash
sudo ufw allow 3000
```
### iptables
```bash
# Allow Docker containers to access host port 3000
sudo iptables -A INPUT -i docker0 -p tcp --dport 3000 -j ACCEPT
```
### firewalld (RHEL/CentOS)
```bash
sudo firewall-cmd --permanent --add-port=3000/tcp
sudo firewall-cmd --reload
```
## Troubleshooting Steps
### 1. Verify Hyprmonitors is Running
```bash
# Check if service is running
systemctl --user status hyprmonitors
# Check if port is listening
ss -tlnp | grep :3000
# Test locally on host
curl http://localhost:3000/health
```
### 2. Check Docker Network
```bash
# See Docker networks
docker network ls
# Inspect Home Assistant container network
docker inspect homeassistant | grep -A 20 NetworkSettings
```
### 3. Debug Container DNS
```bash
# From inside HA container
docker exec -it homeassistant bash
# Test DNS resolution
nslookup host.docker.internal
# Test network connectivity
ping host.docker.internal
# Check what localhost resolves to
ping localhost # Should be 127.0.0.1 (container internal)
```
### 4. Check Home Assistant Logs
```bash
# View HA logs for connection errors
docker logs homeassistant | grep hyprmonitors
# Enable debug logging in Home Assistant configuration.yaml:
# logger:
# default: info
# logs:
# custom_components.hyprmonitors: debug
```
## Platform-Specific Notes
### Docker Desktop (Windows/Mac)
- `host.docker.internal` works automatically
- No extra configuration needed
- Gateway IP is handled transparently
### Docker Engine (Linux)
- Must add `--add-host=host.docker.internal:host-gateway`
- Or use actual host IP address
- Gateway IP varies by Docker version
### Podman
- Use `host.containers.internal` instead of `host.docker.internal`
- May need `--add-host=host.containers.internal:host-gateway`
## Alternative Solutions
### 1. Run Hyprmonitors in Container
Create a Dockerfile for Hyprmonitors and run it in the same Docker network as Home Assistant.
### 2. Use Docker Bridge Network
Create a custom bridge network and connect both containers.
### 3. Use Docker Secrets/Configs
Store connection details in Docker secrets for better security.
## Error Messages Reference
### "Connection timed out"
- **Cause**: Cannot reach the host from container
- **Solution**: Use `host.docker.internal` or host IP
### "Connection refused"
- **Cause**: Service not running or wrong port
- **Solution**: Check if Hyprmonitors daemon is running
### "Name or service not known"
- **Cause**: DNS resolution failure
- **Solution**: Use IP address instead of hostname
### "No route to host"
- **Cause**: Network/firewall blocking connection
- **Solution**: Check firewall rules and Docker network config
## Getting Help
If you're still having issues:
1. Run the connectivity test: `python3 test_connectivity.py`
2. Check Home Assistant logs for detailed error messages
3. Verify your Docker configuration matches the examples above
4. Test the connection manually using curl from inside the container
## Example Working Configuration
Here's a complete working example:
**docker-compose.yml:**
```yaml
version: '3.8'
services:
homeassistant:
container_name: homeassistant
image: ghcr.io/home-assistant/home-assistant:stable
volumes:
- ./config:/config
ports:
- "8123:8123"
extra_hosts:
- "host.docker.internal:host-gateway"
restart: unless-stopped
```
**Home Assistant Integration Config:**
```
Host: host.docker.internal
Port: 3000
```
**Test Command:**
```bash
docker exec -it homeassistant curl http://host.docker.internal:3000/health
```
This configuration should work in most Docker setups and resolve the networking issues between Home Assistant and your Hyprmonitors daemon.

View File

@@ -255,19 +255,62 @@ filter:
## Troubleshooting ## Troubleshooting
### Docker Networking Issues
**Most Common Issue**: If you're running Home Assistant in Docker and getting connection timeouts:
1. **Use Docker host networking**:
- Replace `localhost` with `host.docker.internal` (Docker Desktop)
- Or use your host machine's IP address (e.g., `192.168.1.100`)
- Or add `--network host` to your Docker run command
2. **Docker Compose example**:
```yaml
version: '3'
services:
homeassistant:
container_name: homeassistant
image: ghcr.io/home-assistant/home-assistant:stable
volumes:
- ./config:/config
environment:
- TZ=America/New_York
ports:
- "8123:8123"
extra_hosts:
- "host.docker.internal:host-gateway" # For Docker on Linux
```
3. **Testing connectivity from Docker**:
```bash
# From inside the Home Assistant container:
docker exec -it homeassistant curl http://host.docker.internal:3000/health
# Or test with your host IP:
docker exec -it homeassistant curl http://192.168.1.100:3000/health
```
### Connection Issues ### Connection Issues
1. **Cannot connect to server**: 1. **Cannot connect to server**:
- **Docker users**: Use `host.docker.internal` instead of `localhost`
- Verify the Hyprmonitors server is running: `curl http://localhost:3000/health` - Verify the Hyprmonitors server is running: `curl http://localhost:3000/health`
- Check host and port configuration - Check host and port configuration
- Ensure firewall allows connections - Ensure firewall allows connections on port 3000
2. **Monitors not detected**: 2. **Timeout errors in logs**:
- Check if Home Assistant is in Docker (see Docker section above)
- Verify network connectivity between Home Assistant and hyprmonitors server
- Check if firewall is blocking port 3000
- Try increasing timeout in integration settings
3. **Monitors not detected**:
- Check if Hyprland is running - Check if Hyprland is running
- Verify monitors are detected: `hyprctl monitors` - Verify monitors are detected: `hyprctl monitors`
- Restart the Hyprmonitors server - Restart the Hyprmonitors server
- Check server logs for errors
3. **Integration not loading**: 4. **Integration not loading**:
- Check Home Assistant logs for errors - Check Home Assistant logs for errors
- Verify all files are copied correctly - Verify all files are copied correctly
- Restart Home Assistant completely - Restart Home Assistant completely

View File

@@ -1,6 +1,7 @@
"""Config flow for Hyprland Monitor Control integration.""" """Config flow for Hyprland Monitor Control integration."""
from __future__ import annotations from __future__ import annotations
import asyncio
import logging import logging
from typing import Any from typing import Any
@@ -47,12 +48,17 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
if not result.get("success", False): if not result.get("success", False):
raise CannotConnect raise CannotConnect
except asyncio.TimeoutError:
_LOGGER.error("Timeout connecting to Hyprmonitors API at %s (10s timeout exceeded)", url)
_LOGGER.error("If running Home Assistant in Docker, use host.docker.internal instead of localhost")
raise CannotConnect
except aiohttp.ClientError as err: except aiohttp.ClientError as err:
_LOGGER.error("Error connecting to Hyprmonitors API: %s", err) _LOGGER.error("Error connecting to Hyprmonitors API at %s: %s", url, err)
_LOGGER.error("Make sure the hyprmonitors daemon is running and accessible")
raise CannotConnect from err raise CannotConnect from err
except Exception as err: except Exception as err:
_LOGGER.exception("Unexpected exception") _LOGGER.exception("Unexpected exception connecting to %s", url)
raise InvalidAuth from err raise CannotConnect from err
# Try to get monitor list to verify full functionality # Try to get monitor list to verify full functionality
try: try:
@@ -64,8 +70,11 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
result = await response.json() result = await response.json()
monitors = result.get("monitors", {}) monitors = result.get("monitors", {})
except asyncio.TimeoutError:
_LOGGER.error("Timeout getting monitor status from %s:%s (10s timeout exceeded)", host, port)
raise CannotConnect
except aiohttp.ClientError as err: except aiohttp.ClientError as err:
_LOGGER.error("Error getting monitor status: %s", err) _LOGGER.error("Error getting monitor status from %s:%s: %s", host, port, err)
raise CannotConnect from err raise CannotConnect from err
# Return info that you want to store in the config entry. # Return info that you want to store in the config entry.

View File

@@ -11,6 +11,19 @@ DEFAULT_HOST = "localhost"
DEFAULT_PORT = 3000 DEFAULT_PORT = 3000
DEFAULT_SCAN_INTERVAL = 30 DEFAULT_SCAN_INTERVAL = 30
# Error messages for better user guidance
ERROR_CANNOT_CONNECT = "cannot_connect"
ERROR_TIMEOUT = "timeout"
ERROR_INVALID_RESPONSE = "invalid_response"
ERROR_UNKNOWN = "unknown"
# Docker networking hints
DOCKER_HOST_ALTERNATIVES = [
"host.docker.internal", # Docker Desktop
"172.17.0.1", # Default Docker bridge gateway
"docker.host.internal" # Some Docker variants
]
# Configuration keys # Configuration keys
CONF_HOST = "host" CONF_HOST = "host"
CONF_PORT = "port" CONF_PORT = "port"

View File

@@ -15,9 +15,11 @@
} }
}, },
"error": { "error": {
"cannot_connect": "Failed to connect to the Hyprmonitors server. Please check the host and port.", "cannot_connect": "Failed to connect to the Hyprmonitors server. If running Home Assistant in Docker, try using 'host.docker.internal' instead of 'localhost'. Please check the host and port are correct.",
"timeout": "Connection timed out. If running Home Assistant in Docker, use 'host.docker.internal' or your host machine's IP address instead of 'localhost'.",
"invalid_response": "Invalid response from Hyprmonitors server. Please verify the server is running correctly.",
"invalid_auth": "Authentication failed. Please check your credentials.", "invalid_auth": "Authentication failed. Please check your credentials.",
"unknown": "Unexpected error occurred." "unknown": "Unexpected error occurred. Check Home Assistant logs for details."
}, },
"abort": { "abort": {
"already_configured": "This Hyprmonitors server is already configured." "already_configured": "This Hyprmonitors server is already configured."

View File

@@ -175,10 +175,12 @@ check_hyprmonitors_server() {
else else
print_warning "Hyprmonitors server not reachable at $host:$port" print_warning "Hyprmonitors server not reachable at $host:$port"
print_info "Make sure the Rust server is running: cargo run" print_info "Make sure the Rust server is running: cargo run"
print_info "For Docker users, run: python3 test_connectivity.py to diagnose issues"
return 1 return 1
fi fi
else else
print_warning "curl not available, cannot test server connection" print_warning "curl not available, cannot test server connection"
print_info "Use: python3 test_connectivity.py to test connectivity"
return 1 return 1
fi fi
} }
@@ -192,17 +194,23 @@ show_next_steps() {
echo " - If using Docker: docker restart homeassistant" echo " - If using Docker: docker restart homeassistant"
echo " - If using HAOS: Settings → System → Restart" echo " - If using HAOS: Settings → System → Restart"
echo echo
echo "2. Add the integration:" echo "2. Test connectivity (especially for Docker users):"
echo " - cd $(dirname "$SOURCE_DIR")/homeassistant"
echo " - python3 test_connectivity.py"
echo " - For Docker: python3 test_connectivity.py host.docker.internal 3000"
echo
echo "3. Add the integration:"
echo " - Go to Settings → Devices & Services" echo " - Go to Settings → Devices & Services"
echo " - Click 'Add Integration'" echo " - Click 'Add Integration'"
echo " - Search for 'Hyprland Monitor Control'" echo " - Search for 'Hyprland Monitor Control'"
echo " - Configure with your server details" echo " - Configure with your server details"
echo " - Docker users: Use 'host.docker.internal' instead of 'localhost'"
echo echo
echo "3. Make sure your Hyprmonitors server is running:" echo "4. Make sure your Hyprmonitors server is running:"
echo " - cd $(dirname "$SOURCE_DIR")" echo " - cd $(dirname "$SOURCE_DIR")"
echo " - cargo run" echo " - cargo run"
echo echo
echo "4. Optional: Check the examples.yaml file for automation ideas" echo "5. Optional: Check the examples.yaml file for automation ideas"
echo echo
print_success "Enjoy controlling your monitors from Home Assistant! 🖥️" print_success "Enjoy controlling your monitors from Home Assistant! 🖥️"
} }
@@ -215,11 +223,13 @@ usage() {
echo " -h, --help Show this help message" echo " -h, --help Show this help message"
echo " --check-server HOST:PORT Check if Hyprmonitors server is running" echo " --check-server HOST:PORT Check if Hyprmonitors server is running"
echo " --dry-run Show what would be done without making changes" echo " --dry-run Show what would be done without making changes"
echo " --test-connectivity Run connectivity test with detailed diagnostics"
echo echo
echo "Examples:" echo "Examples:"
echo " $0 # Auto-detect HA config directory" echo " $0 # Auto-detect HA config directory"
echo " $0 -c /config # Specify config directory" echo " $0 -c /config # Specify config directory"
echo " $0 --check-server localhost:3000 # Test server connection" echo " $0 --check-server localhost:3000 # Test server connection"
echo " $0 --test-connectivity # Run full connectivity test"
echo " $0 --dry-run # Preview installation" echo " $0 --dry-run # Preview installation"
} }
@@ -227,6 +237,7 @@ main() {
local ha_config="" local ha_config=""
local check_server="" local check_server=""
local dry_run=false local dry_run=false
local test_connectivity=false
# Parse arguments # Parse arguments
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
@@ -243,6 +254,10 @@ main() {
check_server="$2" check_server="$2"
shift 2 shift 2
;; ;;
--test-connectivity)
test_connectivity=true
shift
;;
--dry-run) --dry-run)
dry_run=true dry_run=true
shift shift
@@ -264,6 +279,24 @@ main() {
exit $? exit $?
fi fi
# Handle connectivity test
if [[ "$test_connectivity" == true ]]; then
local test_script="$SOURCE_DIR/test_connectivity.py"
if [[ -f "$test_script" ]]; then
print_info "Running connectivity test..."
if command -v python3 &> /dev/null; then
python3 "$test_script"
exit $?
else
print_error "Python3 not found, cannot run connectivity test"
exit 1
fi
else
print_error "Connectivity test script not found: $test_script"
exit 1
fi
fi
# Detect Home Assistant config directory # Detect Home Assistant config directory
if [[ -z "$ha_config" ]]; then if [[ -z "$ha_config" ]]; then
ha_config=$(detect_ha_config) ha_config=$(detect_ha_config)

View File

@@ -0,0 +1,239 @@
#!/usr/bin/env python3
"""
Hyprmonitors Home Assistant Connectivity Test
This script helps diagnose connectivity issues between Home Assistant and
the Hyprmonitors server, particularly for Docker setups.
Usage:
python test_connectivity.py [host] [port]
Examples:
python test_connectivity.py
python test_connectivity.py localhost 3000
python test_connectivity.py host.docker.internal 3000
python test_connectivity.py 192.168.1.100 3000
"""
import asyncio
import aiohttp
import sys
import time
from typing import Optional, Tuple
async def test_connection(host: str = "localhost", port: int = 3000) -> bool:
"""Test connection to Hyprmonitors server."""
url = f"http://{host}:{port}/health"
print(f"Testing connection to {url}...")
try:
timeout = aiohttp.ClientTimeout(total=10)
async with aiohttp.ClientSession(timeout=timeout) as session:
start_time = time.time()
async with session.get(url) as response:
end_time = time.time()
response_time = (end_time - start_time) * 1000 # Convert to ms
print(f"✓ Connection successful! Response time: {response_time:.2f}ms")
print(f" Status: {response.status}")
print(f" Content-Type: {response.headers.get('content-type', 'unknown')}")
if response.status == 200:
try:
data = await response.json()
print(f" Response: {data}")
if data.get("success"):
print("✓ Health check passed!")
return True
else:
print("✗ Health check failed - server returned success=false")
return False
except Exception as e:
print(f"✗ Failed to parse JSON response: {e}")
text = await response.text()
print(f" Raw response: {text[:200]}...")
return False
else:
print(f"✗ Server returned status {response.status}")
return False
except asyncio.TimeoutError:
print("✗ Connection timed out (10 seconds)")
print(" This usually means:")
print(" - The server is not running")
print(" - Wrong host/port configuration")
print(" - Network/firewall blocking the connection")
return False
except aiohttp.ClientConnectorError as e:
print(f"✗ Connection failed: {e}")
print(" This usually means:")
print(" - The server is not running")
print(" - Wrong host/port configuration")
print(" - If using Docker: try 'host.docker.internal' instead of 'localhost'")
return False
except Exception as e:
print(f"✗ Unexpected error: {e}")
return False
async def test_monitor_status(host: str = "localhost", port: int = 3000) -> bool:
"""Test monitor status endpoint."""
url = f"http://{host}:{port}/monitors/status"
print(f"\nTesting monitor status endpoint: {url}...")
try:
timeout = aiohttp.ClientTimeout(total=10)
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.get(url) as response:
if response.status == 200:
try:
data = await response.json()
print(f"✓ Monitor status retrieved successfully!")
print(f" Success: {data.get('success')}")
monitors = data.get('monitors', {})
print(f" Found {len(monitors)} monitors:")
for monitor, status in monitors.items():
print(f" - {monitor}: {status}")
return True
except Exception as e:
print(f"✗ Failed to parse monitor status response: {e}")
return False
else:
print(f"✗ Monitor status request failed with status {response.status}")
return False
except Exception as e:
print(f"✗ Monitor status test failed: {e}")
return False
def test_docker_alternatives(port: int = 3000) -> list:
"""Test common Docker host alternatives."""
alternatives = [
"host.docker.internal",
"172.17.0.1",
"docker.host.internal",
"gateway.docker.internal"
]
print("\n" + "="*60)
print("TESTING DOCKER HOST ALTERNATIVES")
print("="*60)
working_hosts = []
for host in alternatives:
print(f"\nTesting {host}:{port}...")
try:
result = asyncio.run(test_connection(host, port))
if result:
working_hosts.append(host)
print(f"{host} works!")
else:
print(f"{host} failed")
except Exception as e:
print(f"{host} error: {e}")
return working_hosts
def print_summary(host: str, port: int, health_ok: bool, status_ok: bool):
"""Print test summary and recommendations."""
print("\n" + "="*60)
print("TEST SUMMARY")
print("="*60)
print(f"Host: {host}")
print(f"Port: {port}")
print(f"Health check: {'✓ PASS' if health_ok else '✗ FAIL'}")
print(f"Monitor status: {'✓ PASS' if status_ok else '✗ FAIL'}")
if health_ok and status_ok:
print("\n🎉 All tests passed! You can use these settings in Home Assistant:")
print(f" Host: {host}")
print(f" Port: {port}")
else:
print("\n❌ Tests failed. Try these troubleshooting steps:")
print("\n1. Make sure the Hyprmonitors server is running:")
print(" systemctl --user status hyprmonitors")
print(" # or if using cargo:")
print(" cargo run --release")
print("\n2. Test locally on the host machine:")
print(f" curl http://localhost:{port}/health")
print("\n3. If Home Assistant is in Docker, try:")
print(" - Use 'host.docker.internal' instead of 'localhost'")
print(" - Use your host machine's IP address")
print(" - Add '--network host' to Docker run command")
print("\n4. Check firewall settings:")
print(f" sudo ufw allow {port}")
print(f" # or check iptables rules")
if host == "localhost":
print("\n5. For Docker users, run this script with Docker alternatives:")
print(" python test_connectivity.py host.docker.internal 3000")
async def main():
"""Main function."""
# Parse command line arguments
host = "localhost"
port = 3000
if len(sys.argv) > 1:
host = sys.argv[1]
if len(sys.argv) > 2:
try:
port = int(sys.argv[2])
except ValueError:
print(f"Invalid port: {sys.argv[2]}")
sys.exit(1)
print("Hyprmonitors Home Assistant Connectivity Test")
print("="*50)
# Test basic connectivity and health
health_ok = await test_connection(host, port)
# Test monitor status endpoint
status_ok = False
if health_ok:
status_ok = await test_monitor_status(host, port)
# If localhost failed and we might be in Docker, test alternatives
if not health_ok and host == "localhost":
print(f"\nLocalhost failed. Testing Docker alternatives...")
working_hosts = test_docker_alternatives(port)
if working_hosts:
print(f"\n✓ Working Docker hosts found: {working_hosts}")
print(f"Recommendation: Use '{working_hosts[0]}' as your host in Home Assistant")
# Test the first working host
print(f"\nTesting monitor status with {working_hosts[0]}...")
status_ok = await test_monitor_status(working_hosts[0], port)
host = working_hosts[0] # Update for summary
health_ok = True # We know it works
# Print summary and recommendations
print_summary(host, port, health_ok, status_ok)
# Exit with appropriate code
sys.exit(0 if (health_ok and status_ok) else 1)
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\n\nTest cancelled by user")
sys.exit(130)
except Exception as e:
print(f"\nUnexpected error: {e}")
sys.exit(1)