diff --git a/config.example.toml b/config.example.toml
index 3d69da9..033019a 100644
--- a/config.example.toml
+++ b/config.example.toml
@@ -4,3 +4,4 @@ username = "hasspy"
 password = "password"
 
 log_level = "INFO"
+interval = 60
diff --git a/hasspy/mqtt.py b/hasspy/mqtt.py
index 3f70a2b..00d9487 100644
--- a/hasspy/mqtt.py
+++ b/hasspy/mqtt.py
@@ -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)