diff --git a/waybar/README.md b/waybar/README.md index dccdbe2..1266b71 100644 --- a/waybar/README.md +++ b/waybar/README.md @@ -5,7 +5,7 @@ socket (`/tmp/fan_controller.sock`) — the same protocol as `FanController.qml` ## Install -1. Copy the script: +1. Copy the files: ```sh mkdir -p ~/.config/waybar/scripts @@ -13,11 +13,40 @@ socket (`/tmp/fan_controller.sock`) — the same protocol as `FanController.qml` chmod +x ~/.config/waybar/scripts/fan.py ``` -2. Add the `custom/fan` entry from [`fan.json`](fan.json) to - `~/.config/waybar/config.json` (merge it into your existing module list, +2. The daemon runs on the Pi, so bridge its Unix socket over SSH once: + + ```sh + cp waybar/fan-tunnel.service ~/.config/systemd/user/ + systemctl --user daemon-reload + systemctl --user enable --now fan-tunnel + ``` + + This forwards `127.0.0.1:10250` → `root@10.55.0.1:/tmp/fan_controller.sock`. + +3. Add the `custom/cooling_pad` entry (below) to + `~/.config/waybar/config.jsonc` (merge it into your existing module list, e.g. under `modules-right`). -3. Restart Waybar. +4. Restart Waybar. + +### waybar config + +```jsonc +"custom/cooling_pad": { + "return-type": "json", + "exec": "~/.config/waybar/scripts/fan.py", + "interval": 5, + "on-click": "python3 ~/.config/waybar/scripts/fan.py cycle", + "on-right-click": "python3 ~/.config/waybar/scripts/fan.py auto" +} +``` + +### Connection details + +`fan.py` uses the local `/tmp/fan_controller.sock` if it exists, otherwise the +tunnel at `127.0.0.1:10250`. Override with `FAN_CONTROLLER_ENDPOINT` +(`unix:/path` or `host:port`). If the widget shows `fan: offline`, check +`systemctl --user status fan-tunnel` and `ssh root@10.55.0.1 'ls -la /tmp/fan_controller.sock'`. ## Usage diff --git a/waybar/fan-tunnel.service b/waybar/fan-tunnel.service new file mode 100644 index 0000000..6e57cb7 --- /dev/null +++ b/waybar/fan-tunnel.service @@ -0,0 +1,16 @@ +[Unit] +Description=SSH tunnel for Pi fan_controller socket (waybar widget) +After=network-online.target +Wants=network-online.target + +[Service] +ExecStart=/usr/bin/ssh -N \ + -o ExitOnForwardFailure=yes \ + -o ServerAliveInterval=15 \ + -o ServerAliveCountMax=3 \ + -L 10250:/tmp/fan_controller.sock root@10.55.0.1 +Restart=always +RestartSec=5 + +[Install] +WantedBy=default.target diff --git a/waybar/fan.json b/waybar/fan.json index 1aeba68..0498590 100644 --- a/waybar/fan.json +++ b/waybar/fan.json @@ -1,5 +1,5 @@ { - "custom/fan": { + "custom/cooling_pad": { "return-type": "json", "exec": "~/.config/waybar/scripts/fan.py", "interval": 5, diff --git a/waybar/fan.py b/waybar/fan.py index 16da6e7..cfba813 100755 --- a/waybar/fan.py +++ b/waybar/fan.py @@ -9,20 +9,43 @@ Usage: fan.py auto switch back to auto mode (on-right-click) fan.py duty 75 set manual duty, 0-100 fan.py setpoint 55 set auto-mode setpoint, 35-85 + +Connection: the daemon's command socket lives on the Pi. This script uses the +local Unix socket if present, otherwise a TCP tunnel to it (default +127.0.0.1:10250, provided by the fan-tunnel systemd service: + ssh -N -L 10250:/tmp/fan_controller.sock root@10.55.0.1). +Override with FAN_CONTROLLER_ENDPOINT: "unix:/path" or "host:port". """ import json import os import socket import sys -SOCK = os.environ.get("FAN_CONTROLLER_SOCK", "/tmp/fan_controller.sock") +UNIX_PATH = "/tmp/fan_controller.sock" +TUNNEL_HOST, TUNNEL_PORT = "127.0.0.1", 10250 STEPS = [25, 50, 75, 100] +def make_socket(): + endpoint = os.environ.get("FAN_CONTROLLER_ENDPOINT") + if endpoint is None: + if os.path.exists(UNIX_PATH): + endpoint = f"unix:{UNIX_PATH}" + else: + endpoint = f"{TUNNEL_HOST}:{TUNNEL_PORT}" + if endpoint.startswith("unix:"): + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.connect(endpoint[len("unix:"):]) + else: + host, port = endpoint.rsplit(":", 1) + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((host, int(port))) + return s + + def cmd(command: str) -> str: - with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s: + with make_socket() as s: s.settimeout(2) - s.connect(SOCK) s.sendall(command.encode() + b"\n") chunks = [] while True: