Problem statement
LG Buddy currently controls the TV through the Python bscpylgtvcommand CLI. The Rust runtime shells out to that command through BscpylgtvCommandClient, and the installer creates a Python virtual environment under the install prefix so bscpylgtv is available at runtime.
This has become a poor fit for the direction of the project:
- it makes the install payload larger and more fragile
- it requires
python3-venv, python3-pip, and network/package-manager behavior during install unless the release bundle skips it
- it complicates immutable distro support because the Python venv must be placed and wired correctly outside
/usr
- it forces TV errors through subprocess stdout/stderr parsing instead of structured runtime errors
- it keeps auth/key ownership tied to bscpylgtv's
.aiopylgtv.sqlite storage
The target model is a self-contained Rust runtime that speaks the LG webOS TV API directly.
This should be a replacement, not a long-lived dual backend. Avoid adding a permanent runtime choice between Python and native TV control.
Current Rust boundary
The existing shape is favorable for a clean replacement:
tv.rs already defines a narrow TvClient trait
- screen and lifecycle policy use
TvDevice / TvClient rather than directly invoking Python
- most Python-specific code is contained in
BscpylgtvCommandClient, subprocess launchers, and auth-owner handling
Target behavior
Implement a native WebOsTvClient that satisfies the existing LG Buddy TvClient contract:
- query current input
- set configured HDMI input
- query OLED/backlight brightness
- set OLED/backlight brightness
- power off
- turn screen off
- turn screen on
Expected webOS surfaces, based on the current bscpylgtv behavior:
| LG Buddy operation |
webOS/SSAP surface |
| current input |
com.webos.applicationManager/getForegroundAppInfo |
| set HDMI input |
tv/switchInput with inputId |
| power state guard |
com.webos.service.tvpower/power/getPowerState |
| power off |
system/turnOff |
| screen off |
com.webos.service.tvpower/power/turnOffScreen with standbyMode: active |
| screen on |
com.webos.service.tvpower/power/turnOnScreen with standbyMode: active |
| get picture settings |
settings/getSystemSettings for category picture |
| set picture settings |
likely needs the same Luna/settings path currently used by bscpylgtv |
Screen on/off may need endpoint fallback for older webOS versions:
com.webos.service.tvpower/power/turnOffScreen
com.webos.service.tvpower/power/turnOnScreen
- older fallback:
com.webos.service.tv.power/turnOffScreen
- older fallback:
com.webos.service.tv.power/turnOnScreen
Auth and pairing
Native auth should be owned by LG Buddy, not by bscpylgtv.
Preliminary target:
~/.config/lg-buddy/webos-client-key.json
Requirements:
- create/update the native key file as the config owner, not as root
- use file permissions appropriate for local credential material, e.g.
0600
- pair through the normal TV prompt when no key exists
- preserve clear error output when pairing is required or rejected
A one-time migration helper from .aiopylgtv.sqlite is acceptable if it is simple and well-tested, but it should not become a permanent runtime fallback. If migration is not reliable enough, requiring one re-pair during the native transition is acceptable.
Implementation direction
- Add native webOS transport primitives:
- websocket connection to
wss://<tv-ip>:3001
- TLS verification disabled as bscpylgtv currently does
- registration payload with the required manifest and existing/new client key
- request IDs and response correlation
- request timeout and connection retry behavior
- Add structured native TV errors:
- connection failure
- pairing required/rejected
- response timeout
- webOS error response with preserved error code/message
- unsupported endpoint
- Implement
WebOsTvClient for the existing TvClient trait.
- Replace runtime construction sites so LG Buddy uses the native client directly.
- Remove Python/subprocess-specific TV runtime code:
BscpylgtvCommandClient
- command launcher types
- Python helper env vars, except any short-lived migration-only handling
- stderr parsing for known webOS codes where native structured errors can replace it
- Update installer/release code:
- stop checking for
python3-venv and python3-pip
- stop creating
LG_Buddy_PIP
- stop installing
bscpylgtv
- remove
LG_BUDDY_BSCPYLGTV_COMMAND wiring
- Update docs and architecture overview to describe native webOS TV control.
Testing scope
Unit/integration tests should use a mock webOS websocket server rather than a real TV.
Coverage should include:
- connect and register with an existing client key
- connect and register through a new pairing response
- rejected/failed pairing
- request/response correlation by ID
- malformed response handling
- webOS error response mapping, including preserving codes such as
-102
- current-input query
- set-input request payload
- power-off guard behavior using power state
- screen off/on request payloads and older endpoint fallback
- brightness get/set payloads and parsing
- auth key file ownership/path/permission behavior where practical
Release smoke coverage should assert that the installed bundle no longer creates a Python virtual environment or requires bscpylgtvcommand.
Manual validation should cover at least:
- first-time pairing on a real LG webOS TV
- existing paired TV after restart/reboot
- screen off/on from idle and lock-screen activity
- suspend pre-sleep power-off and resume restore
- brightness command
- behavior when the TV is off, active standby, or unreachable
Dependency relationship
This is a prerequisite for first-class immutable distro support. Tracked in #24.
References
- Current Python bridge:
crates/lg-buddy/src/tv.rs
- Current auth-owner helper:
crates/lg-buddy/src/auth.rs
- Current installer Python venv setup:
install.sh
Migrated from #26 before repository ownership transfer.
Original issue: #26
Original author: @Staphylococcus
Original created: 2026-04-30T17:26:57Z
Original comments at migration time: none.
Problem statement
LG Buddy currently controls the TV through the Python
bscpylgtvcommandCLI. The Rust runtime shells out to that command throughBscpylgtvCommandClient, and the installer creates a Python virtual environment under the install prefix sobscpylgtvis available at runtime.This has become a poor fit for the direction of the project:
python3-venv,python3-pip, and network/package-manager behavior during install unless the release bundle skips it/usr.aiopylgtv.sqlitestorageThe target model is a self-contained Rust runtime that speaks the LG webOS TV API directly.
This should be a replacement, not a long-lived dual backend. Avoid adding a permanent runtime choice between Python and native TV control.
Current Rust boundary
The existing shape is favorable for a clean replacement:
tv.rsalready defines a narrowTvClienttraitTvDevice/TvClientrather than directly invoking PythonBscpylgtvCommandClient, subprocess launchers, and auth-owner handlingTarget behavior
Implement a native
WebOsTvClientthat satisfies the existing LG BuddyTvClientcontract:Expected webOS surfaces, based on the current bscpylgtv behavior:
com.webos.applicationManager/getForegroundAppInfotv/switchInputwithinputIdcom.webos.service.tvpower/power/getPowerStatesystem/turnOffcom.webos.service.tvpower/power/turnOffScreenwithstandbyMode: activecom.webos.service.tvpower/power/turnOnScreenwithstandbyMode: activesettings/getSystemSettingsfor categorypictureScreen on/off may need endpoint fallback for older webOS versions:
com.webos.service.tvpower/power/turnOffScreencom.webos.service.tvpower/power/turnOnScreencom.webos.service.tv.power/turnOffScreencom.webos.service.tv.power/turnOnScreenAuth and pairing
Native auth should be owned by LG Buddy, not by bscpylgtv.
Preliminary target:
Requirements:
0600A one-time migration helper from
.aiopylgtv.sqliteis acceptable if it is simple and well-tested, but it should not become a permanent runtime fallback. If migration is not reliable enough, requiring one re-pair during the native transition is acceptable.Implementation direction
wss://<tv-ip>:3001WebOsTvClientfor the existingTvClienttrait.BscpylgtvCommandClientpython3-venvandpython3-pipLG_Buddy_PIPbscpylgtvLG_BUDDY_BSCPYLGTV_COMMANDwiringTesting scope
Unit/integration tests should use a mock webOS websocket server rather than a real TV.
Coverage should include:
-102Release smoke coverage should assert that the installed bundle no longer creates a Python virtual environment or requires
bscpylgtvcommand.Manual validation should cover at least:
Dependency relationship
This is a prerequisite for first-class immutable distro support. Tracked in #24.
References
crates/lg-buddy/src/tv.rscrates/lg-buddy/src/auth.rsinstall.shMigrated from #26 before repository ownership transfer.
Original issue: #26
Original author: @Staphylococcus
Original created: 2026-04-30T17:26:57Z
Original comments at migration time: none.