Add user mode support with separate client classes for system and user modes

This commit is contained in:
Edgar P. Burkhart 2025-03-09 12:26:57 +01:00
parent cbb6c71f09
commit 77aa8b5b92
Signed by: edpibu
GPG key ID: 9833D3C5A25BD227
2 changed files with 97 additions and 37 deletions

View file

@ -3,7 +3,7 @@ import tomllib
from argparse import ArgumentParser
from pathlib import Path
from hasspy.mqtt import HassClient
from hasspy.mqtt import HassClient, HassSystemClient, HassUserClient
def main() -> None:
@ -15,6 +15,7 @@ def main() -> None:
parser.add_argument(
"-v", "--verbose", help="Enable verbose logging", action="count", default=0
)
parser.add_argument("-u", "--user", help="User mode client", action="store_true")
args = parser.parse_args()
if args.config:
@ -41,10 +42,17 @@ def main() -> None:
level=config.get("log_level", logging.INFO) - (args.verbose * 10)
)
ha = HassClient(
"orchomenos",
config=config,
)
ha: HassClient
if not args.user:
ha = HassSystemClient(
"orchomenos",
config=config,
)
else:
ha = HassUserClient(
"orchomenos",
config=config,
)
ha.loop_forever()

View file

@ -1,3 +1,4 @@
import getpass
import json
import logging
from subprocess import run
@ -16,11 +17,6 @@ class HassClient(Client):
self.node_id = node_id
self.config = config
self.state_topic = f"{self.node_id}/state"
self.availability_topic = f"{self.node_id}/availability"
self.command_topic = f"{self.node_id}/set"
self.discovery_topic = f"homeassistant/device/{self.node_id}/config"
username = self.config.get("username")
if username:
self.username_pw_set(username, self.config.get("password"))
@ -62,11 +58,20 @@ class HassClient(Client):
def publish_state(self) -> MQTTMessageInfo:
return self.publish_json(
topic=self.state_topic,
payload={
"power": "POWER_ON" if self.power_on else "POWER_OFF",
},
payload=self.state_payload,
)
def on_command(self, client: Client, userdata: Any, message: MQTTMessage) -> None:
payload = message.payload.decode("utf-8")
log.debug(f"Received command: {payload}")
self.do_command(payload)
self.publish_state()
def do_command(self, payload: str) -> None:
log.debug(f"Executing command: {payload}")
def on_connect(self, *args: Any, **kwargs: Any) -> None:
log.info("Connected to MQTT broker")
self.publish_discovery()
@ -75,9 +80,50 @@ class HassClient(Client):
self.publish_state()
def on_command(self, client: Client, userdata: Any, message: MQTTMessage) -> None:
payload = message.payload.decode("utf-8")
log.debug(f"Received command: {payload}")
@property
def state_topic(self) -> str:
return f"{self.node_id}/state"
@property
def availability_topic(self) -> str:
return f"{self.node_id}/availability"
@property
def command_topic(self) -> str:
return f"{self.node_id}/set"
@property
def discovery_topic(self) -> str:
return f"homeassistant/device/{self.node_id}/config"
@property
def discovery_payload(self) -> dict[str, Any]:
return {
"dev": {
"ids": self.node_id,
"name": self.node_id,
},
"o": {
"name": "hasspy",
},
"cmps": self.components,
"availability_topic": self.availability_topic,
"command_topic": self.command_topic,
"state_topic": self.state_topic,
}
@property
def state_payload(self) -> dict[str, Any]:
return {}
@property
def components(self) -> dict[str, dict[str, str]]:
return {}
class HassSystemClient(HassClient):
def do_command(self, payload: str) -> None:
super().do_command(payload)
match payload:
case "POWER_ON":
@ -96,30 +142,36 @@ class HassClient(Client):
proc = run(["systemctl", "poweroff", "--when=+1m"])
if proc.returncode != 0:
log.error("Failed to schedule shutdown")
self.publish_state()
case "LOCK":
log.info("Locking screen…")
run(["loginctl", "lock-sessions"])
@property
def discovery_payload(self) -> dict[str, Any]:
def components(self) -> dict[str, dict[str, str]]:
return {
"dev": {
"ids": self.node_id,
"name": self.node_id,
"power": {
"unique_id": f"{self.node_id}_power",
"p": "switch",
"name": "Power",
"payload_off": "POWER_OFF",
"payload_on": "POWER_ON",
"value_template": "{{ value_json.power }}",
},
"o": {
"name": "hasspy",
"lock": {
"unique_id": f"{self.node_id}_power",
"p": "button",
"name": "Lock",
"payload_press": "LOCK",
},
"cmps": {
"power": {
"unique_id": f"{self.node_id}_power",
"p": "switch",
"name": "Power",
"payload_off": "POWER_OFF",
"payload_on": "POWER_ON",
"value_template": "{{ value_json.power }}",
},
},
"availability_topic": self.availability_topic,
"command_topic": self.command_topic,
"state_topic": self.state_topic,
}
@property
def state_payload(self) -> dict[str, Any]:
return {
"power": self.power_on,
}
class HassUserClient(HassClient):
def __init__(self, node_id: str, config: Mapping[str, Any]) -> None:
super().__init__(f"{node_id}_{getpass.getuser()}", config)