From 4a47277c86d9eee425473a950174a4121143e1f5 Mon Sep 17 00:00:00 2001 From: Florian Egger Date: Fri, 11 Sep 2026 10:54:00 +0200 Subject: [PATCH] Add remote fan tunnel support and rename Waybar widget Add systemd service to bridge the Pi Unix socket over SSH, make fan.py fall back to the TCP tunnel, and rename the module from `custom/fan` to `custom/cooling_pad`. Update README install steps and connection details. --- waybar/README.md | 37 +++++++++++++++++++++++++++++++++---- waybar/fan-tunnel.service | 16 ++++++++++++++++ waybar/fan.json | 2 +- waybar/fan.py | 29 ++++++++++++++++++++++++++--- 4 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 waybar/fan-tunnel.service 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: