forked from sstent/minihass
working
This commit is contained in:
58
README.md
58
README.md
@@ -73,3 +73,61 @@ services:
|
|||||||
[](https://github.com/OWNER/REPO/actions/workflows/container-build.yml)
|
[](https://github.com/OWNER/REPO/actions/workflows/container-build.yml)
|
||||||
|
|
||||||
> Replace OWNER/REPO with your GitHub username and repository name
|
> Replace OWNER/REPO with your GitHub username and repository name
|
||||||
|
|
||||||
|
### Nomad Deployment
|
||||||
|
For production deployments, use this Nomad job specification:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
job "minihass" {
|
||||||
|
datacenters = ["dc1"]
|
||||||
|
|
||||||
|
group "smart-home" {
|
||||||
|
network {
|
||||||
|
mode = "host"
|
||||||
|
port "http" {
|
||||||
|
to = 5000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
service {
|
||||||
|
name = "minihass"
|
||||||
|
port = "http"
|
||||||
|
|
||||||
|
check {
|
||||||
|
type = "http"
|
||||||
|
path = "/health"
|
||||||
|
interval = "30s"
|
||||||
|
timeout = "5s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task "app" {
|
||||||
|
driver = "docker"
|
||||||
|
|
||||||
|
config {
|
||||||
|
image = "ghcr.io/your-username/your-repo:latest"
|
||||||
|
ports = ["http"]
|
||||||
|
}
|
||||||
|
|
||||||
|
env {
|
||||||
|
CONSUL_HOST = "consul.service.dc1.consul"
|
||||||
|
CONSUL_PORT = "8500"
|
||||||
|
TPLINK_IP = "192.168.1.100"
|
||||||
|
TV_IP = "192.168.1.101"
|
||||||
|
TV_MAC = "AA:BB:CC:DD:EE:FF"
|
||||||
|
}
|
||||||
|
|
||||||
|
resources {
|
||||||
|
cpu = 500
|
||||||
|
memory = 256
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Deployment Steps:
|
||||||
|
1. Install and configure Nomad cluster
|
||||||
|
2. Update environment variables in the job file
|
||||||
|
3. Run: `nomad job run minihass.nomad.hcl`
|
||||||
|
4. Access the app at: `http://<nomad-node-ip>:5000`
|
||||||
87
app.py
87
app.py
@@ -21,9 +21,14 @@ import time
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from aiowebostv import WebOsClient, endpoints as ep
|
||||||
|
|
||||||
|
# Add missing endpoint
|
||||||
|
ep.GET_POWER_STATE = "com.webos.service.tvpower/power/getPowerState"
|
||||||
|
|
||||||
# Configure logging for container
|
# Configure logging for container
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.INFO,
|
level=logging.DEBUG,
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||||
handlers=[
|
handlers=[
|
||||||
logging.StreamHandler(sys.stdout)
|
logging.StreamHandler(sys.stdout)
|
||||||
@@ -171,25 +176,60 @@ class WebOSTV:
|
|||||||
|
|
||||||
async def _execute_command(self, command):
|
async def _execute_command(self, command):
|
||||||
"""Execute TV command with automatic connection handling"""
|
"""Execute TV command with automatic connection handling"""
|
||||||
from aiowebostv import WebOsClient
|
client = None
|
||||||
try:
|
try:
|
||||||
client_key = self.load_client_key()
|
client_key = self.load_client_key()
|
||||||
async with WebOsClient(self.ip, client_key) as client:
|
logger.debug(f"Attempting WebOS connection to {self.ip} with client key: {client_key}")
|
||||||
# If we have a new client key after connection, save it
|
|
||||||
if client.client_key and client.client_key != client_key:
|
# Handle both context manager and manual connection for library compatibility
|
||||||
self.save_client_key(client.client_key)
|
client = WebOsClient(self.ip, client_key)
|
||||||
|
if hasattr(client, '__aenter__'):
|
||||||
|
# Use context manager if available
|
||||||
|
async with client as client:
|
||||||
|
return await self._handle_client_commands(client, client_key, command)
|
||||||
|
else:
|
||||||
|
# Manual connection for older library versions
|
||||||
|
await client.connect()
|
||||||
|
client.connected = True
|
||||||
|
result = await self._handle_client_commands(client, client_key, command)
|
||||||
|
await client.disconnect()
|
||||||
|
client.connected = False
|
||||||
|
return result
|
||||||
|
|
||||||
if command == "turn_off":
|
|
||||||
await client.turn_off()
|
|
||||||
return True
|
|
||||||
elif command == "turn_on":
|
|
||||||
await client.turn_on()
|
|
||||||
return True
|
|
||||||
elif command == "get_power":
|
|
||||||
return client.power_state == "on"
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"WebOS TV error: {e}")
|
logger.error(f"WebOS TV error: {e}", exc_info=True)
|
||||||
return None
|
return None
|
||||||
|
finally:
|
||||||
|
# Ensure cleanup if manual connection was used
|
||||||
|
if client and not hasattr(client, '__aenter__') and getattr(client, 'connected', False):
|
||||||
|
await client.disconnect()
|
||||||
|
|
||||||
|
async def _handle_client_commands(self, client, original_key, command):
|
||||||
|
"""Handle TV commands after successful connection"""
|
||||||
|
logger.debug(f"Connected to WebOS TV. Client key: {client.client_key}")
|
||||||
|
|
||||||
|
# Save new client key if generated
|
||||||
|
if client.client_key and client.client_key != original_key:
|
||||||
|
logger.info(f"Saving new client key for TV at {self.ip}")
|
||||||
|
self.save_client_key(client.client_key)
|
||||||
|
|
||||||
|
# Execute requested command using screen-specific methods
|
||||||
|
if command == "turn_off":
|
||||||
|
logger.debug("Sending turn_off_screen command")
|
||||||
|
result = await client.command("request", ep.TURN_OFF_SCREEN)
|
||||||
|
logger.debug(f"Turn off command result: {result}, type: {type(result)}")
|
||||||
|
return True
|
||||||
|
elif command == "turn_on":
|
||||||
|
logger.debug("Sending turn_on_screen command")
|
||||||
|
await client.command("request", ep.TURN_ON_SCREEN)
|
||||||
|
return True
|
||||||
|
elif command == "get_power":
|
||||||
|
logger.debug("Getting screen state")
|
||||||
|
# Use proper method to get screen state
|
||||||
|
return await self._get_screen_state(client)
|
||||||
|
|
||||||
|
logger.warning(f"Unknown command received: {command}")
|
||||||
|
return None
|
||||||
|
|
||||||
async def turn_screen_off(self):
|
async def turn_screen_off(self):
|
||||||
"""Turn off TV screen"""
|
"""Turn off TV screen"""
|
||||||
@@ -200,9 +240,24 @@ class WebOSTV:
|
|||||||
return await self._execute_command("turn_on")
|
return await self._execute_command("turn_on")
|
||||||
|
|
||||||
async def get_power_state(self):
|
async def get_power_state(self):
|
||||||
"""Get TV power state"""
|
"""Get TV screen state"""
|
||||||
return await self._execute_command("get_power")
|
return await self._execute_command("get_power")
|
||||||
|
|
||||||
|
async def _get_screen_state(self, client):
|
||||||
|
try:
|
||||||
|
result = await client.request(ep.GET_POWER_STATE)
|
||||||
|
power_state = result.get('state')
|
||||||
|
logger.debug(f"Raw power state: {power_state}")
|
||||||
|
|
||||||
|
if power_state in ["Active", "Screen On"]:
|
||||||
|
return True
|
||||||
|
elif power_state in ["Power Off", "Screen Off"]:
|
||||||
|
return False
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting screen state: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def update_device_state(device, state):
|
def update_device_state(device, state):
|
||||||
"""Thread-safe device state update"""
|
"""Thread-safe device state update"""
|
||||||
|
|||||||
43
nomad/minihass.nomad.hcl
Normal file
43
nomad/minihass.nomad.hcl
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
job "minihass" {
|
||||||
|
datacenters = ["dc1"]
|
||||||
|
|
||||||
|
group "smart-home" {
|
||||||
|
network {
|
||||||
|
mode = "host"
|
||||||
|
port "http" {
|
||||||
|
to = 5000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
service {
|
||||||
|
name = "minihass"
|
||||||
|
port = "http"
|
||||||
|
|
||||||
|
check {
|
||||||
|
type = "http"
|
||||||
|
path = "/health"
|
||||||
|
interval = "30s"
|
||||||
|
timeout = "5s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task "app" {
|
||||||
|
driver = "docker"
|
||||||
|
|
||||||
|
config {
|
||||||
|
image = "ghcr.io/sstent/MiniHASS:latest"
|
||||||
|
ports = ["http"]
|
||||||
|
}
|
||||||
|
|
||||||
|
env {
|
||||||
|
CONSUL_HOST = "consul.service.dc1.consul"
|
||||||
|
CONSUL_PORT = "8500"
|
||||||
|
}
|
||||||
|
|
||||||
|
resources {
|
||||||
|
cpu = 500
|
||||||
|
memory = 256
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,5 +2,5 @@ flask==2.3.3
|
|||||||
websockets==12.0
|
websockets==12.0
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
gunicorn==21.2.0
|
gunicorn==21.2.0
|
||||||
aiowebostv==0.8.0
|
aiowebostv==0.7.5
|
||||||
python-consul==1.1.0
|
python-consul==1.1.0
|
||||||
|
|||||||
Reference in New Issue
Block a user