Refactor main entry point and command handling in HassSystemClient and HassUserClient
This commit is contained in:
parent
9b3df6416e
commit
fad234ad00
4 changed files with 104 additions and 85 deletions
|
@ -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
|
|
@ -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()
|
|
||||||
|
|
|
@ -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]]:
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Add table
Reference in a new issue