Add volume control and state publishing to HassUserClient
This commit is contained in:
parent
b5a3846cc5
commit
204e0a1fbc
2 changed files with 68 additions and 25 deletions
|
@ -4,3 +4,4 @@ username = "hasspy"
|
|||
password = "password"
|
||||
|
||||
log_level = "INFO"
|
||||
interval = 60
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import json
|
||||
import logging
|
||||
from subprocess import run
|
||||
from threading import Timer
|
||||
from typing import Any, Mapping
|
||||
|
||||
from paho.mqtt.client import Client, MQTTMessage, MQTTMessageInfo
|
||||
|
@ -20,7 +21,9 @@ class HassClient(Client):
|
|||
if username:
|
||||
self.username_pw_set(username, self.config.get("password"))
|
||||
|
||||
self.interval = self.config.get("interval", 60)
|
||||
self.power_on = True
|
||||
self.timer = Timer(self.interval, self.publish_state)
|
||||
|
||||
self.connect()
|
||||
|
||||
|
@ -55,6 +58,11 @@ class HassClient(Client):
|
|||
self.message_callback_add(self.command_topic, self.on_command)
|
||||
|
||||
def publish_state(self) -> MQTTMessageInfo:
|
||||
self.timer.cancel()
|
||||
self.timer = Timer(self.interval, self.publish_state)
|
||||
self.timer.start()
|
||||
|
||||
log.debug("Publishing state message")
|
||||
return self.publish_json(
|
||||
topic=self.state_topic,
|
||||
payload=self.state_payload,
|
||||
|
@ -64,12 +72,12 @@ class HassClient(Client):
|
|||
payload = message.payload.decode("utf-8")
|
||||
log.debug(f"Received command: {payload}")
|
||||
|
||||
self.do_command(payload)
|
||||
self.do_command(*payload.split(":"))
|
||||
|
||||
self.publish_state()
|
||||
|
||||
def do_command(self, payload: str) -> None:
|
||||
log.debug(f"Executing command: {payload}")
|
||||
def do_command(self, cmd: str, value: str = "") -> None:
|
||||
pass
|
||||
|
||||
def on_connect(self, *args: Any, **kwargs: Any) -> None:
|
||||
log.info("Connected to MQTT broker")
|
||||
|
@ -116,7 +124,7 @@ class HassClient(Client):
|
|||
return {}
|
||||
|
||||
@property
|
||||
def components(self) -> dict[str, dict[str, str]]:
|
||||
def components(self) -> dict[str, dict[str, Any]]:
|
||||
return {}
|
||||
|
||||
|
||||
|
@ -127,23 +135,20 @@ class HassSystemClient(HassClient):
|
|||
"LOCK": ["loginctl", "lock-sessions"],
|
||||
}
|
||||
|
||||
def do_command(self, payload: str) -> None:
|
||||
if payload not in self.commands:
|
||||
return
|
||||
def do_command(self, cmd: str, value: str = "") -> None:
|
||||
if cmd in self.commands:
|
||||
log.debug(f"Executing command: {cmd}")
|
||||
proc = run(self.commands[cmd])
|
||||
if proc.returncode != 0:
|
||||
log.error(f"Failed to execute command: {cmd}")
|
||||
|
||||
super().do_command(payload)
|
||||
|
||||
proc = run(self.commands[payload])
|
||||
if proc.returncode != 0:
|
||||
log.error(f"Failed to execute command: {payload}")
|
||||
|
||||
if payload == "POWER_ON":
|
||||
if cmd == "POWER_ON":
|
||||
self.power_on = True
|
||||
elif payload == "POWER_OFF":
|
||||
elif cmd == "POWER_OFF":
|
||||
self.power_on = False
|
||||
|
||||
@property
|
||||
def components(self) -> dict[str, dict[str, str]]:
|
||||
def components(self) -> dict[str, dict[str, Any]]:
|
||||
return {
|
||||
"power": {
|
||||
"unique_id": f"{self.node_id}_power",
|
||||
|
@ -176,22 +181,33 @@ class HassUserClient(HassClient):
|
|||
def __init__(self, node_id: str, config: Mapping[str, Any]) -> None:
|
||||
super().__init__(f"{node_id}", config)
|
||||
|
||||
def do_command(self, payload: str) -> None:
|
||||
if payload not in self.commands:
|
||||
return
|
||||
def do_command(self, cmd: str, value: str = "") -> None:
|
||||
if cmd in self.commands:
|
||||
log.debug(f"Executing command: {cmd}")
|
||||
proc = run(self.commands[cmd])
|
||||
if proc.returncode != 0:
|
||||
log.error(f"Failed to execute command: {cmd}")
|
||||
|
||||
super().do_command(payload)
|
||||
|
||||
proc = run(self.commands[payload])
|
||||
if proc.returncode != 0:
|
||||
log.error(f"Failed to execute command: {payload}")
|
||||
match [cmd, value]:
|
||||
case ["VOLUME", value]:
|
||||
log.debug(f"Executing command: {cmd}:{value}")
|
||||
proc = run(
|
||||
[
|
||||
"wpctl",
|
||||
"set-volume",
|
||||
"@DEFAULT_AUDIO_SINK@",
|
||||
f"{int(value) / 100:.2f}",
|
||||
]
|
||||
)
|
||||
if proc.returncode != 0:
|
||||
log.error(f"Failed to set volume: {value}")
|
||||
|
||||
@property
|
||||
def availability_topic(self) -> str:
|
||||
return f"{self.node_id}/user/availability"
|
||||
|
||||
@property
|
||||
def components(self) -> dict[str, dict[str, str]]:
|
||||
def components(self) -> dict[str, dict[str, Any]]:
|
||||
return {
|
||||
"play-pause": {
|
||||
"unique_id": f"{self.node_id}_play_pause",
|
||||
|
@ -199,4 +215,30 @@ class HassUserClient(HassClient):
|
|||
"name": "Play/Pause",
|
||||
"payload_press": "PLAY_PAUSE",
|
||||
},
|
||||
"volume": {
|
||||
"unique_id": f"{self.node_id}_volume",
|
||||
"p": "number",
|
||||
"name": "Volume",
|
||||
"command_template": "VOLUME:{{ value }}",
|
||||
"step": 10,
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"unit_of_measurement": "%",
|
||||
"value_template": "{{ value_json.volume }}",
|
||||
},
|
||||
}
|
||||
|
||||
@property
|
||||
def state_payload(self) -> dict[str, Any]:
|
||||
return {
|
||||
"volume": self.volume_value,
|
||||
}
|
||||
|
||||
@property
|
||||
def volume_value(self) -> int:
|
||||
proc = run(["wpctl", "get-volume", "@DEFAULT_AUDIO_SINK@"], capture_output=True)
|
||||
if proc.returncode != 0:
|
||||
log.error("Failed to get volume")
|
||||
return 0
|
||||
|
||||
return int(float(proc.stdout.decode("utf-8").split(": ")[1]) * 100)
|
||||
|
|
Loading…
Add table
Reference in a new issue