From 8f2bd2b015e20476ffc03f7a9d64b224a539dbef Mon Sep 17 00:00:00 2001 From: Mortdecai Date: Mon, 1 Jun 2026 20:16:20 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20power=5Foff=20works=20under=20systemd=20?= =?UTF-8?q?=E2=80=94=20absolute=20key=20path=20+=20freshly-woken=20retry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two OFF-path bugs found during live testing: - bscpylgtv's key store defaulted to a CWD-relative ./.aiopylgtv.sqlite; under systemd (cwd=/) it couldn't open the pairing key db. Pass an absolute key_file_path (LGTV_KEY_FILE override, else ~/.aiopylgtv.sqlite). - A TV woken seconds earlier answers TCP :3000 but rejects WebSocket control for ~20-30s. Retry power_off (2 attempts, 10s apart); common case still succeeds first try in ~3s. Kept under the bridge's 60s subprocess timeout. Co-Authored-By: Claude Opus 4.8 (1M context) --- lgtv.sh | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/lgtv.sh b/lgtv.sh index 3867158..09512d7 100755 --- a/lgtv.sh +++ b/lgtv.sh @@ -138,12 +138,23 @@ tv_off() { exit 1 fi echo "TV at $ip — sending WebOS power_off..." - TV_IP="$ip" python3 - <<'PY' + # Retry a few times: a TV woken seconds ago answers TCP :3000 but isn't + # ready for a WebSocket control session for ~20-30s. The common case + # (TV already up) succeeds on the first try in ~3s. + local attempt + for attempt in 1 2; do + if TV_IP="$ip" python3 - <<'PY' import asyncio, os from bscpylgtv import WebOsClient +# Pass the key store path explicitly. bscpylgtv otherwise defaults to a +# CWD-relative ./.aiopylgtv.sqlite (and its $USER_HOME fallback isn't $HOME), +# so under systemd (cwd=/) it can't find the TV's pairing key. Absolute path +# = works regardless of cwd/env. Override with LGTV_KEY_FILE if needed. +KEY = os.environ.get('LGTV_KEY_FILE') or os.path.expanduser('~/.aiopylgtv.sqlite') + async def main(): - client = await WebOsClient.create(os.environ['TV_IP']) + client = await WebOsClient.create(os.environ['TV_IP'], key_file_path=KEY) await client.connect() await client.power_off() await asyncio.sleep(1) @@ -152,7 +163,15 @@ async def main(): asyncio.run(main()) print('power_off sent') PY - echo "Done." + then + echo "Done." + return 0 + fi + echo " power_off attempt $attempt failed (TV may still be waking); retrying..." + sleep 10 + done + echo "ERROR: power_off failed after 2 attempts" + exit 1 } tv_status() {