Refactor main entry point and command handling in HassSystemClient and HassUserClient

This commit is contained in:
Edgar P. Burkhart 2025-03-09 12:59:30 +01:00
parent 9b3df6416e
commit fad234ad00
Signed by: edpibu
GPG key ID: 9833D3C5A25BD227
4 changed files with 104 additions and 85 deletions

View file

@ -0,0 +1,68 @@
import logging
import signal
import tomllib
from argparse import ArgumentParser
from pathlib import Path
from hasspy.mqtt import HassClient, HassSystemClient, HassUserClient
log = logging.getLogger(__name__)
def main() -> int:
log.info("Starting HassPy")
parser = ArgumentParser(
prog="HassPy",
description="Home Assistant MQTT client",
)
parser.add_argument("-c", "--config", help="Path to configuration file")
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:
config_file = Path(args.config)
else:
config_file = next(
(
x
for x in (Path("/etc/hasspy.toml"), Path("/etc/hasspy/config.toml"))
if x.exists()
),
Path("config.toml"),
)
if not config_file or not config_file.exists():
raise FileNotFoundError("No configuration file found")
with open(config_file, "rb") as file:
config = tomllib.load(file)
if isinstance(config.get("log_level"), str):
config["log_level"] = getattr(logging, config["log_level"])
logging.basicConfig(
level=config.get("log_level", logging.INFO) - (args.verbose * 10)
)
ha: HassClient
if not args.user:
ha = HassSystemClient(
"orchomenos",
config=config,
)
else:
ha = HassUserClient(
"orchomenos",
config=config,
)
ha.loop_start()
signal.sigwait([signal.SIGHUP, signal.SIGINT, signal.SIGTERM])
log.info("Shutting down")
ha.loop_stop()
return 0

View file

@ -1,61 +1,3 @@
import logging from hasspy import main
import tomllib
from argparse import ArgumentParser
from pathlib import Path
from hasspy.mqtt import HassClient, HassSystemClient, HassUserClient main()
def main() -> None:
parser = ArgumentParser(
prog="HassPy",
description="Home Assistant MQTT client",
)
parser.add_argument("-c", "--config", help="Path to configuration file")
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:
config_file = Path(args.config)
else:
config_file = next(
(
x
for x in (Path("/etc/hasspy.toml"), Path("/etc/hasspy/config.toml"))
if x.exists()
),
Path("config.toml"),
)
if not config_file or not config_file.exists():
raise FileNotFoundError("No configuration file found")
with open(config_file, "rb") as file:
config = tomllib.load(file)
if isinstance(config.get("log_level"), str):
config["log_level"] = getattr(logging, config["log_level"])
logging.basicConfig(
level=config.get("log_level", logging.INFO) - (args.verbose * 10)
)
ha: HassClient
if not args.user:
ha = HassSystemClient(
"orchomenos",
config=config,
)
else:
ha = HassUserClient(
"orchomenos",
config=config,
)
ha.loop_forever()
if __name__ == "__main__":
main()

View file

@ -1,4 +1,3 @@
import getpass
import json import json
import logging import logging
from subprocess import run from subprocess import run
@ -122,29 +121,26 @@ class HassClient(Client):
class HassSystemClient(HassClient): class HassSystemClient(HassClient):
commands = {
"POWER_ON": ["systemctl", "poweroff", "--when=cancel"],
"POWER_OFF": ["systemctl", "poweroff", "--when=+1m"],
"LOCK": ["loginctl", "lock-sessions"],
}
def do_command(self, payload: str) -> None: def do_command(self, payload: str) -> None:
if payload not in self.commands:
return
super().do_command(payload) super().do_command(payload)
match payload: proc = run(self.commands[payload])
case "POWER_ON": if proc.returncode != 0:
if not self.power_on: log.error(f"Failed to execute command: {payload}")
log.info("Cancelling shutdown…")
self.power_on = True
proc = run(["systemctl", "poweroff", "--when=cancel"]) if payload == "POWER_ON":
if proc.returncode != 0: self.power_on = True
log.error("Failed to cancel shutdown") elif payload == "POWER_OFF":
case "POWER_OFF": self.power_on = False
if self.power_on:
log.info("Powering off…")
self.power_on = False
proc = run(["systemctl", "poweroff", "--when=+1m"])
if proc.returncode != 0:
log.error("Failed to schedule shutdown")
case "LOCK":
log.info("Locking screen…")
run(["loginctl", "lock-sessions"])
@property @property
def components(self) -> dict[str, dict[str, str]]: def components(self) -> dict[str, dict[str, str]]:
@ -173,16 +169,26 @@ class HassSystemClient(HassClient):
class HassUserClient(HassClient): class HassUserClient(HassClient):
commands = {
"PLAY_PAUSE": ["playerctl", "play-pause"],
}
def __init__(self, node_id: str, config: Mapping[str, Any]) -> None: def __init__(self, node_id: str, config: Mapping[str, Any]) -> None:
super().__init__(f"{node_id}_{getpass.getuser()}", config) super().__init__(f"{node_id}", config)
def do_command(self, payload: str) -> None: def do_command(self, payload: str) -> None:
if payload not in self.commands:
return
super().do_command(payload) super().do_command(payload)
match payload: proc = run(self.commands[payload])
case "PLAY_PAUSE": if proc.returncode != 0:
log.info("Toggling play/pause…") log.error(f"Failed to execute command: {payload}")
run(["playerctl", "play-pause"])
@property
def availability_topic(self) -> str:
return f"{self.node_id}/user/availability"
@property @property
def components(self) -> dict[str, dict[str, str]]: def components(self) -> dict[str, dict[str, str]]:

View file

@ -13,3 +13,6 @@ dev = [
"mypy>=1.15.0", "mypy>=1.15.0",
"ruff>=0.9.10", "ruff>=0.9.10",
] ]
[project.scripts]
hasspy = "hasspy:main"