Add user mode support with separate client classes for system and user modes
This commit is contained in:
parent
cbb6c71f09
commit
77aa8b5b92
2 changed files with 97 additions and 37 deletions
hasspy
|
@ -3,7 +3,7 @@ import tomllib
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from hasspy.mqtt import HassClient
|
from hasspy.mqtt import HassClient, HassSystemClient, HassUserClient
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
|
@ -15,6 +15,7 @@ def main() -> None:
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-v", "--verbose", help="Enable verbose logging", action="count", default=0
|
"-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()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.config:
|
if args.config:
|
||||||
|
@ -41,10 +42,17 @@ def main() -> None:
|
||||||
level=config.get("log_level", logging.INFO) - (args.verbose * 10)
|
level=config.get("log_level", logging.INFO) - (args.verbose * 10)
|
||||||
)
|
)
|
||||||
|
|
||||||
ha = HassClient(
|
ha: HassClient
|
||||||
"orchomenos",
|
if not args.user:
|
||||||
config=config,
|
ha = HassSystemClient(
|
||||||
)
|
"orchomenos",
|
||||||
|
config=config,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
ha = HassUserClient(
|
||||||
|
"orchomenos",
|
||||||
|
config=config,
|
||||||
|
)
|
||||||
|
|
||||||
ha.loop_forever()
|
ha.loop_forever()
|
||||||
|
|
||||||
|
|
116
hasspy/mqtt.py
116
hasspy/mqtt.py
|
@ -1,3 +1,4 @@
|
||||||
|
import getpass
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from subprocess import run
|
from subprocess import run
|
||||||
|
@ -16,11 +17,6 @@ class HassClient(Client):
|
||||||
self.node_id = node_id
|
self.node_id = node_id
|
||||||
self.config = config
|
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")
|
username = self.config.get("username")
|
||||||
if username:
|
if username:
|
||||||
self.username_pw_set(username, self.config.get("password"))
|
self.username_pw_set(username, self.config.get("password"))
|
||||||
|
@ -62,11 +58,20 @@ class HassClient(Client):
|
||||||
def publish_state(self) -> MQTTMessageInfo:
|
def publish_state(self) -> MQTTMessageInfo:
|
||||||
return self.publish_json(
|
return self.publish_json(
|
||||||
topic=self.state_topic,
|
topic=self.state_topic,
|
||||||
payload={
|
payload=self.state_payload,
|
||||||
"power": "POWER_ON" if self.power_on else "POWER_OFF",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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:
|
def on_connect(self, *args: Any, **kwargs: Any) -> None:
|
||||||
log.info("Connected to MQTT broker")
|
log.info("Connected to MQTT broker")
|
||||||
self.publish_discovery()
|
self.publish_discovery()
|
||||||
|
@ -75,9 +80,50 @@ class HassClient(Client):
|
||||||
|
|
||||||
self.publish_state()
|
self.publish_state()
|
||||||
|
|
||||||
def on_command(self, client: Client, userdata: Any, message: MQTTMessage) -> None:
|
@property
|
||||||
payload = message.payload.decode("utf-8")
|
def state_topic(self) -> str:
|
||||||
log.debug(f"Received command: {payload}")
|
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:
|
match payload:
|
||||||
case "POWER_ON":
|
case "POWER_ON":
|
||||||
|
@ -96,30 +142,36 @@ class HassClient(Client):
|
||||||
proc = run(["systemctl", "poweroff", "--when=+1m"])
|
proc = run(["systemctl", "poweroff", "--when=+1m"])
|
||||||
if proc.returncode != 0:
|
if proc.returncode != 0:
|
||||||
log.error("Failed to schedule shutdown")
|
log.error("Failed to schedule shutdown")
|
||||||
|
case "LOCK":
|
||||||
self.publish_state()
|
log.info("Locking screen…")
|
||||||
|
run(["loginctl", "lock-sessions"])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def discovery_payload(self) -> dict[str, Any]:
|
def components(self) -> dict[str, dict[str, str]]:
|
||||||
return {
|
return {
|
||||||
"dev": {
|
"power": {
|
||||||
"ids": self.node_id,
|
"unique_id": f"{self.node_id}_power",
|
||||||
"name": self.node_id,
|
"p": "switch",
|
||||||
|
"name": "Power",
|
||||||
|
"payload_off": "POWER_OFF",
|
||||||
|
"payload_on": "POWER_ON",
|
||||||
|
"value_template": "{{ value_json.power }}",
|
||||||
},
|
},
|
||||||
"o": {
|
"lock": {
|
||||||
"name": "hasspy",
|
"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)
|
||||||
|
|
Loading…
Add table
Reference in a new issue