Click on Download Project Bundle button below to download the code.py and other files. Connect the PyPortal to your computer via a known good data+power USB cable. The PyPortal should show up in your operating system file explorer as a drive called CIRCUITPY.

# SPDX-FileCopyrightText: 2019 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

import time
import math
import board
import busio
from digitalio import DigitalInOut
import neopixel
from adafruit_esp32spi import adafruit_esp32spi
from adafruit_esp32spi import adafruit_esp32spi_wifimanager
import displayio
import adafruit_touchscreen
import adafruit_imageload

# Set up the touchscreen
ts = adafruit_touchscreen.Touchscreen(
    board.TOUCH_XL,
    board.TOUCH_XR,
    board.TOUCH_YD,
    board.TOUCH_YU,
    calibration=((5200, 59000), (5800, 57000)),
    size=(320, 240),
)

# Get wifi details and more from a secrets.py file
try:
    from secrets import secrets
except ImportError:
    print("WiFi secrets are kept in secrets.py, please add them there!")
    raise

# If you are using a board with pre-defined ESP32 Pins:
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)

spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)

status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)

wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)

# Set the ip of your Roku here
ip = "192.168.1.3"

"""
Possible keypress key values to send the Roku
Home
Rev
Fwd
Play
Select
Left
Right
Down
Up
Back
InstantReplay
Info
Backspace
Search
Enter
FindRemote

Keypress key values that only work on smart TVs with built-in Rokus
VolumeDown
VolumeMute
VolumeUp
PowerOff
ChannelUp
ChannelDown
"""


def getchannels():
    """ Gets the channels installed on the device. Also useful because it
        verifies that the PyPortal can see the Roku"""
    try:
        print("Getting channels. Usually takes around 10 seconds...", end="")
        response = wifi.get("http://{}:8060/query/apps".format(ip))
        channel_dict = {}
        for i in response.text.split("<app")[2:]:
            a = i.split("=")
            chan_id = a[1].split('"')[1]
            name = a[-1].split(">")[1].split("<")[0]
            channel_dict[name] = chan_id
        response.close()
        return channel_dict
    except (ValueError, RuntimeError, ConnectionError, OSError) as e:
        print("Failed to get data\n", e)
        wifi.reset()
        return None
    response = None
    return None


def sendkey(key):
    """ Sends a key to the Roku """
    try:
        print("Sending key: {}...".format(key), end="")
        response = wifi.post("http://{}:8060/keypress/{}".format(ip, key))
        if response:
            response.close()
            print("OK")
    except (ValueError, RuntimeError, ConnectionError, OSError) as e:
        print("Failed to get data\n", e)
        wifi.reset()
        return
    response = None


def sendletter(letter):
    """ Sends a letter to the Roku, not used in this guide """
    try:
        print("Sending letter: {}...".format(letter), end="")
        response = wifi.post("http://{}:8060/keypress/lit_{}".format(ip, letter))
        if response:
            response.close()
            print("OK")
    except (ValueError, RuntimeError, ConnectionError, OSError) as e:
        print("Failed to get data\n", e)
        wifi.reset()
        return
    response = None


def openchannel(channel):
    """ Tells the Roku to open the channel with the corresponding channel id """
    try:
        print("Opening channel: {}...".format(channel), end="")
        response = wifi.post("http://{}:8060/launch/{}".format(ip, channel))
        if response:
            response.close()
            print("OK")
        response = None
    except (ValueError, RuntimeError, ConnectionError, OSError) as e:
        print("Probably worked, but got error\n", e)
        wifi.reset()
    response = None


def switchpage(tup):
    """ Used to switch to a different page """
    p_num = tup[0]
    tile_grid = tup[1]
    new_page = pages[p_num - 1]
    new_page_vals = pages_vals[p_num - 1]
    my_display_group[-1] = tile_grid
    return new_page, new_page_vals


# Verifies the Roku and Pyportal are connected and visible
channels = getchannels()

my_display_group = displayio.Group()

image_1, palette_1 = adafruit_imageload.load(
    "images/page_1.bmp", bitmap=displayio.Bitmap, palette=displayio.Palette
)
tile_grid_1 = displayio.TileGrid(image_1, pixel_shader=palette_1)
my_display_group.append(tile_grid_1)

image_2, palette_2 = adafruit_imageload.load(
    "images/page_2.bmp", bitmap=displayio.Bitmap, palette=displayio.Palette
)
tile_grid_2 = displayio.TileGrid(image_2, pixel_shader=palette_2)

image_3, palette_3 = adafruit_imageload.load(
    "images/page_3.bmp", bitmap=displayio.Bitmap, palette=displayio.Palette
)
tile_grid_3 = displayio.TileGrid(image_3, pixel_shader=palette_3)

# fmt: off
# Page 1
page_1 = [sendkey, sendkey, sendkey,
          sendkey, sendkey, sendkey,
          sendkey, sendkey, sendkey,
          sendkey, sendkey, switchpage]

page_1_vals = ["Back", "Up", "Home",
               "Left", "Select", "Right",
               "Search", "Down", "Info",
               "Rev", "Play", (2, tile_grid_2)]

# Page 2
page_2 = [openchannel, openchannel, openchannel,
          openchannel, openchannel, openchannel,
          openchannel, openchannel, openchannel,
          switchpage, sendkey, switchpage]

page_2_vals = [14362, 2285, 13,
               12, 8378, 837,
               38820, 47389, 7767,
               (1, tile_grid_1), "Home", (3, tile_grid_3)]

page_3 = [None, None, None,
          sendkey, None, sendkey,
          sendkey, sendkey, sendkey,
          switchpage, sendkey, sendkey]

page_3_vals = [None, None, None,
               "Search", None, "Info",
               "Rev", "Play", "Fwd",
               (2, tile_grid_2), "Back", "Home"]
# fmt: on

pages = [page_1, page_2, page_3]
pages_vals = [page_1_vals, page_2_vals, page_3_vals]

page = page_1
page_vals = page_1_vals

board.DISPLAY.root_group = my_display_group
print("READY")


last_index = 0
while True:
    p = ts.touch_point
    if p:
        x = math.floor(p[0] / 80)
        y = abs(math.floor(p[1] / 80) - 2)
        index = 3 * x + y
        # Used to prevent the touchscreen sending incorrect results
        if last_index == index:
            if page[index]:
                # pylint: disable=comparison-with-callable
                if page[index] == switchpage:
                    page, page_vals = switchpage(page_vals[index])
                else:
                    page[index](page_vals[index])
                time.sleep(0.1)

        last_index = index

Copy all the files from the zip file to the PyPortal CIRCUITPY drive. The files on CIRCUITPY should be similar to the directory listing below.

CIRCUITPY

This guide was first published on Jul 09, 2020. It was last updated on Jun 14, 2024.

This page (CircuitPython Setup and Code) was last updated on Jun 14, 2024.

Text editor powered by tinymce.