This note is about scripting two very ordinary devices: a Samsung TV and a Google Chromecast. Both are controlled in Python as part of a smartly built dumb house.

Scripting the TV

Samsung TVs with a LAN port or Wi-Fi connection can be controlled remotely through a simple protocol, just like pressing buttons on the remote control. Android has various apps for this; I used SamyGo Remote. Here we will look at talking to the TV from Python using samsungctl, which does the necessary work.

First, remote control has to be enabled on the TV. For my Samsung UE37ES5500 it was in Network / AllShare settings. Then create a configuration:

REMOTE_CONFIG = {
    "host": "192.168.3.6",
    "port": 55000,
    "name": "Chromak MQTT",
    "description": "MQTT connector for Samsung TV",
    "id": "chromak",
    "timeout": 0.5,
    "method": "legacy",
}

The important item is host, the TV’s IP address; it is useful to assign a fixed IP in DHCP. port is the default port on which the TV listens. id, name, and description are arbitrary strings, and the TV remembers clients with these identifiers. On first access, it shows a dialog asking whether remote control from this device is allowed. timeout is set on the communication socket. On a local network it does not need to be high. method, in my case "legacy", selects the communication method. Newer TVs also support "websocket" according to samsungctl.

After installing samsungctl, ideally with pip install samsungctl, use is simple:

from samsungctl import Remote

with Remote(REMOTE_CONFIG) as remote:
    remote.control("KEY_DTV")

"KEY_DTV" is a button code selected from the supported key codes. "KEY_POWEROFF" is also worth mentioning: it turns the TV off, although a powered-off TV cannot be turned on this way.

I first tried to recycle and keep an active connection, but sending a code with a new connection each time is so fast on the LAN that reconnecting is not worth solving.

Scripting Google Chromecast

Google Chromecast is a handy box, especially if you use Google services on a phone. Besides Chromecast, I also have a JBL Playlist, a simple speaker with Chromecast support. This led me to pychromecast, which can connect to and control Chromecast devices. The examples below came from the source code and the examples, because the documentation was not very talkative.

Connecting to Chromecast

First, Chromecast has to be found on the network and an object created for control:

import pychromecast

casts = pychromecast.get_chromecasts()

In my case, JBL Playlist did not appear in casts, so I used an alternative that does not rely on local-network broadcast and lets me predefine IP addresses:

KNOWN_CHROMECASTS = [
    ("192.168.1.8", 8009, None, "Chromecast", u"TV Obývák"),
    ("192.168.1.242", 8009, None, "Chromecast Audio", u"Ložnice"),
]

casts = []
for cast_args in KNOWN_CHROMECASTS:
    cast = pychromecast._get_chromecast_from_host(cast_args)
    casts.append(cast)
    lst = ChromeListener(cast)
    cast.media_controller.register_status_listener(lst)
    cast.register_status_listener(lst)

The fields in KNOWN_CHROMECASTS are ip_address, port, uuid, model_name, and friendly_name.

Now we have casts, a list of objects for interacting with Chromecasts, and each Chromecast has an associated ChromeListener instance for observing its state.

Observing and Changing Chromecast State

The first thing I scripted was automatic volume adjustment when new media starts playing, whether music or video. The maximum audio volume from Chromecast is significantly higher than volume from the TV. Through ChromeListener, I observe current volume and player state and react when needed:

VOLUME_THRESHOLD = 0.4

PLAYER_STATE_BUFFERING = "BUFFERING"
PLAYER_STATE_UNKNOWN = "UNKNOWN"
PLAYER_STATE_IDLE = "IDLE"
PLAYER_STATE_PAUSED = "PAUSED"
PLAYER_STATE_PLAYING = "PLAYING"

class ChromeListener(object):
    def __init__(self, cast):
        self.cast = cast

        self.last_player_state = None
        self.volume_level = None

    def new_media_status(self, status):
        player_state = status.player_state
        if player_state == PLAYER_STATE_BUFFERING:
            return

        if self.last_player_state in (PLAYER_STATE_UNKNOWN, PLAYER_STATE_IDLE) and (player_state in [PLAYER_STATE_PLAYING, PLAYER_STATE_PAUSED]):
            # Playback just started
            if self.volume_level > VOLUME_THRESHOLD:
                self.cast.set_volume(VOLUME_THRESHOLD*0.95)

        self.last_player_state = player_state

    def new_cast_status(self, status):
        self.volume_level = status.volume_level

If playback starts and the current volume is above the threshold, I set it slightly below the threshold with set_volume. Buffering state is ignored.

Turning the TV on with Chromecast

As mentioned, the Samsung TV cannot be turned on remotely through its LAN protocol. My TV + receiver + Chromecast setup can, however, turn the TV on through HDMI when new media starts on Chromecast. That means it can be done from Python:

cast.play_media("/wp-content/uploads/2014/06/cropped-header.jpg", "image/jpeg")
time.sleep(5)
cast.quit_app()

The TV starts with the active HDMI input and Chromecast returns to its home screen. Calling quit_app() ends playback quickly enough that you do not even see the blog header image.

More Scripting Ideas

Scripting can be used for many small conveniences. I mainly use these:

  • When leaving the house, detected through MQTT from the security-system state, I turn off the TV, JBL Playlist, and the currently running Chromecast application.

  • In the morning, when the children get up and turn on the TV, I stop series at 7:00 and start a radio stream:

    cast.play_media(STREAM_URL, "audio/mpeg")

    At 7:25 I stop the stream:

    cast.quit_app()

    This helps the children know what time it is and how much they need to speed up so that we leave at 7:30. To avoid turning on the TV when, for example, there are holidays and nobody is watching, I read the state of the UniFi controller to see whether the TV is online.

  • Everything described here is part of one process that also listens on MQTT and connects to the rest of the home automation.

Conclusion

If hardware and software are combined cleverly, even hardware limitations can be worked around. A simple script can control many functions and create a few small but useful everyday improvements.