pywebostv

repository·master·Indexed 43 days ago

https://github.com/supersaiyanmode/pywebostv

A Python SDK for controlling LG WebOS TVs. It provides functionality for media playback, system notifications, power management, application control, and mouse/keyboard emulation via InputControl. The library supports both blocking and non-blocking API calls, subscriptions for state changes, and secure WebSockets for newer TV models.

Tokens
3K
Snippets
11
Records
12
Agent score
6%

What's inside pywebostv

  1. How to turn on the TV using WakeOnLAN (WOL)

    master

    PyWebOSTV controls the TV via the WebSocket API, which requires the TV to be powered on and connected to the network. This library does not include a built-in WakeOnLAN (WOL) implementation to avoid unnecessary transitive dependencies.

    To turn on a sleeping TV, you should:

    1. Enable WOL in your TV settings.
    2. Obtain the TV's MAC address.
    3. Use a separate WOL library to send a 'magic packet' to the TV's MAC address.
  2. How API calls and subscriptions work

    master

    The WebOSClient instance is the central communication channel. All control classes (e.g., MediaControl, SystemControl) share this connection.

    API Execution Patterns

    • Blocking Calls: The default behavior. The call waits for a response. These can throw exceptions if arguments are invalid or the call fails.
    • Non-blocking Calls: Use block=False and provide a callback function. The callback receives (status, payload), where status is a boolean indicating success and payload is the response data or an error string.
    • Subscriptions: If an API supports it, you can subscribe to changes using control.subscribe_<api_name>(callback). To stop receiving updates, use control.unsubscribe_<api_name>().

    Note: It is an error to subscribe more than once on the same underlying connection.

    # Non-blocking call with callback
    def my_callback(status, payload):
        if status:
            print("Success:", payload)
        else:
            print("Error:", payload)
    
    control.async_api(arg1, callback=my_callback)
    
    # Subscription
    control.subscribe_api(my_callback)
    control.unsubscribe_api()
  3. Install pywebostv on macOS Big Sur

    master

    If you encounter errors like opensslv.h file not found, you need to install OpenSSL via Homebrew and configure the environment variables so pip can find the headers and libraries during the installation of cryptography.

    $ brew install openssl
    $ env LDFLAGS="-L$(brew --prefix openssl)/lib" CFLAGS="-I$(brew --prefix openssl)/include" pip3 install cryptography
    $ pip3 install pywebostv
  4. Establish a connection to the WebOS TV

    master

    To connect to a TV, you must handle the authentication process using a store dictionary. This dictionary holds the access token required to avoid repeated authentication prompts on the TV.

    Connection Workflow

    1. Initialize Store: For the first run, use an empty dictionary {}. For subsequent runs, load your previously saved store from disk.
    2. Discover or Connect:
      • Use WebOSClient.discover() to scan the network (use discover(secure=True) for newer models).
      • Alternatively, connect directly via IP: WebOSClient("<IP Address>") or WebOSClient("<IP Address>", secure=True).
    3. Register: Call client.register(store). This will trigger an authentication prompt on the TV. Iterate through the generator to monitor status.
    4. Persist: Once client.register(store) returns WebOSClient.REGISTERED, the store object will be populated with the client_key. Save this store to disk for future use.
    from pywebostv.discovery import WebOSClient
    from pywebostv.connection import WebOSClient
    
    # 1. Load or initialize store
    store = load_from_your_custom_storage() if not_empty else {}
    
    # 2. Connect (using discovery or direct IP)
    client = WebOSClient.discover(secure=True)[0]
    client.connect()
    
    # 3. Register and handle authentication
    for status in client.register(store):
        if status == WebOSClient.PROMPTED:
            print("Please accept the connect on the TV!")
        elif status == WebOSClient.REGISTERED:
            print("Registration successful!")
    
    # 4. Persist the populated store
    persist_to_your_custom_storage(store)
  5. How to handle TV authentication and the `store` object

    master

    To avoid repeated authentication prompts on the TV, you must correctly manage the store object.

    1. First Run: Pass an empty dictionary {} as the store. This triggers an authentication prompt on the TV.
    2. Registration: Once the user completes the registration on the TV, the store object is populated with the necessary credentials.
    3. Persistence: You must save (persist) the contents of the store to a database, config file, or other permanent storage.
    4. Subsequent Runs: Load the saved data and pass it back into the client as the store. If you pass an empty object again, the TV will prompt for re-authentication.

    Pro-tip: The client.register(...) method expects an object that implements __getitem__ and __setitem__. You can create a custom class that automatically reads from and writes to your storage within these methods to automate persistence.

    # Conceptual pattern for automatic persistence
    # client.register expects an object with __getitem__ and __setitem__
    class PersistentStore:
        def __getitem__(self, key):
            return load_from_disk(key)
        def __setitem__(self, key, value):
            save_to_disk(key, value)
  6. Fix 'Connection reset by peer' errors using secure WebSockets

    master

    If you encounter Connection reset by peer errors, especially after a TV firmware update, it is likely because newer models require secure WebSockets.

    To resolve this, set the secure flag to True when calling discover() or initializing WebOSClient().

    # Example fix
    client = WebOSClient(host='192.168.1.x', secure=True)
    # OR
    tv = discover(host='192.168.1.x', secure=True)
  7. Use TvControl and SourceControl

    master

    These classes handle channel management and input source switching.

    TvControl

    • channel_up() / channel_down(): Change channels.
    • channel_list(): Returns available channels.
    • get_current_channel(): Returns the current channel.
    • get_current_program(): Returns the current channel and EPG (Electronic Program Guide) data.
    • set_channel_with_id(channelId): Switches to a specific channel ID.

    SourceControl

    • list_sources(): Returns a list of InputSource instances.
    • set_source(InputSource): Switches the TV to the specified input source.
    tv = TvControl(client)
    tv.channel_up()
    
    source = SourceControl(client)
    sources = source.list_sources()
    source.set_source(sources[0])
  8. Use SystemControl for notifications and power

    master

    The SystemControl class provides access to system-level functions like notifications and power management.

    Key Methods

    • notify(message, icon_bytes=None, icon_ext=None): Displays a notification on the TV. icon_bytes allows passing raw image data (e.g., from requests.get().content).
    • power_off(): Turns off the TV. (Note: Programmatic power-on usually requires Wake-on-LAN).
    • screen_off() / screen_on(): Controls the TV's energy-saving screen state.
    • info(): Returns a dictionary containing TV metadata like product_name, model_name, major_ver, and minor_ver.
    system = SystemControl(client)
    system.notify("Hello World!")
    system.info()
  9. Use InputControl for mouse and keyboard

    master

    The InputControl class provides keyboard and mouse emulation.

    Important: Input commands require a separate connection. You must call connect_input() before sending commands and disconnect_input() when finished.

    Key Methods

    • type(string): Sends keystrokes (requires the on-screen keyboard to be visible).
    • enter(), delete(count), backspace(): Keyboard navigation.
    • move(x, y): Moves the mouse pointer.
    • click(): Performs a mouse click or center 'OK' action.
    • up(), down(), left(), right(), home(), back(): Directional and navigation keys.
    • num_0() through num_9(): Number keys.
    • red(), green(), yellow(), blue(): Color buttons.
    • play(), pause(), stop(), fastforward(), rewind(): Media transport keys.
    inp = InputControl(client)
    inp.connect_input()
    inp.type("Hello!")
    inp.enter()
    inp.move(100, 100)
    inp.click()
    inp.disconnect_input()
  10. Use MediaControl for volume and playback

    master

    The MediaControl class manages audio levels, playback state, and audio output sources.

    Key Methods

    • volume_up() / volume_down(): Adjust volume by 1 unit.
    • get_volume(): Returns a dict: {'scenario': str, 'volume': int, 'muted': bool}. (Supports subscription via subscribe_get_volume)
    • set_volume(int): Sets volume from 1 to 100.
    • mute(bool): Mutes or unmutes the TV.
    • play(), pause(), stop(), rewind(), fast_forward(): Standard playback controls.
    • get_audio_output(): Returns an AudioOutputSource instance. (Supports subscription via subscribe_get_audio_output)
    • list_audio_output_sources(): Returns a list of available AudioOutputSource instances.
    • set_audio_output(AudioOutputSource): Sets the active audio output.
    media = MediaControl(client)
    media.set_volume(20)
    media.play()
    
    # Subscription example
    def on_volume_change(status, payload):
        print(payload)
    
    media.subscribe_get_volume(on_volume_change)
  11. Use ApplicationControl to manage apps

    master

    The ApplicationControl class allows you to list, launch, and identify running applications.

    Key Methods

    • list_apps(): Returns a list of Application instances.
    • launch(app_instance, content_id=None): Launches an application. You can optionally pass a content_id to launch specific content (e.g., a specific video URL).
    • close(app_instance): Closes the specified application.
    • get_current(): Returns the application ID (string) of the foreground app. (Supports subscription via subscribe_get_current)

    Finding an App

    Applications are typically identified by searching the list returned by list_apps() for a matching title.

    app = ApplicationControl(client)
    apps = app.list_apps()
    
    # Find and launch YouTube
    yt = [x for x in apps if "youtube" in x["title"].lower()][0]
    app.launch(yt, content_id="v=dQw4w9WgXcQ")
    
    # Get foreground app ID
    current_id = app.get_current()