Libraries

We'll need to make sure we have these libraries installed. (Check out this link on installing libraries if needed.)

  • adafruit_bitmap_font
  • adafruit_bus_device
  • adafruit_display_shapes
  • adafruit_display_text
  • adafruit_matrixportal
  • adafruit_esp32spi
  • adafruit_io
  • adafruit_matrixportal
  • adafruit_requests.mpy
  • neopixel.mpy

Connect to the Internet

Once you have CircuitPython setup and libraries installed we can get your board connected to the Internet. The process for connecting can be found here.

Text Editor

Adafruit recommends using the Mu editor for editing your CircuitPython code. You can get more info in this guide.

Alternatively, you can use any text editor that saves simple text files.

Code

Copy the code from the code-block below and paste it into the Mu editor and save it to your Metro M4 Airlift as code.py (or copy code.py from the zip file and place on the CIRCUITPY drive).

# SPDX-FileCopyrightText: 2020 John Park for Adafruit Industries
#
# SPDX-License-Identifier: MIT

# ON AIR sign for YouTube livestreaming
# Runs on Airlift Metro M4 with 64x32 RGB Matrix display & shield

import time
import board
import displayio
import adafruit_display_text.label
from adafruit_display_shapes.rect import Rect
from adafruit_display_shapes.polygon import Polygon
from adafruit_bitmap_font import bitmap_font
from adafruit_matrixportal.network import Network
from adafruit_matrixportal.matrix import Matrix

# 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

# Set up where we'll be fetching data from
# Adafruit YouTube channel:
CHANNEL_ID = (
    "UCpOlOeQjj7EsVnDh3zuCgsA"  # this isn't a secret but you have to look it up
)

DATA_SOURCE = (
    "https://www.googleapis.com/youtube/v3/search?part=snippet&channelId="
    + CHANNEL_ID
    + "&type=video&eventType=live&key="
    + secrets["youtube_token"]
)
DATA_LOCATION1 = ["items"]

# Number of seconds between checking, if this is too quick the query quota will run out
UPDATE_DELAY = 300

# Times are in 24-hour format for simplification
OPERATING_TIME_START = "12:00"  # what hour to start checking
OPERATING_TIME_END = "19:00"  # what hour to stop checking

# --- Display setup ---
matrix = Matrix()
display = matrix.display
network = Network(status_neopixel=board.NEOPIXEL, debug=False)

# --- Drawing setup ---
# Create a Group
group = displayio.Group()
# Create a bitmap object
bitmap = displayio.Bitmap(64, 32, 2)  # width, height, bit depth
# Create a color palette
color = displayio.Palette(4)
color[0] = 0x000000  # black
color[1] = 0xFF0000  # red
color[2] = 0x444444  # dim white
color[3] = 0xDD8000  # gold
# Create a TileGrid using the Bitmap and Palette
tile_grid = displayio.TileGrid(bitmap, pixel_shader=color)
# Add the TileGrid to the Group
group.append(tile_grid)

# draw the frame for startup
rect1 = Rect(0, 0, 2, 32, fill=color[2])
rect2 = Rect(62, 0, 2, 32, fill=color[2])
rect3 = Rect(2, 0, 9, 2, fill=color[0])
rect4 = Rect(53, 0, 9, 2, fill=color[0])
rect5 = Rect(2, 30, 12, 2, fill=color[0])
rect6 = Rect(50, 30, 12, 2, fill=color[0])

group.append(rect1)
group.append(rect2)
group.append(rect3)
group.append(rect4)
group.append(rect5)
group.append(rect6)


def redraw_frame():  # to adjust spacing at bottom later
    rect3.fill = color[2]
    rect4.fill = color[2]
    rect5.fill = color[2]
    rect6.fill = color[2]


# draw the wings w polygon shapes
wing_polys = []

wing_polys.append(Polygon([(3, 3), (9, 3), (9, 4), (4, 4)], outline=color[1]))
wing_polys.append(Polygon([(5, 6), (9, 6), (9, 7), (6, 7)], outline=color[1]))
wing_polys.append(Polygon([(7, 9), (9, 9), (9, 10), (8, 10)], outline=color[1]))
wing_polys.append(Polygon([(54, 3), (60, 3), (59, 4), (54, 4)], outline=color[1]))
wing_polys.append(Polygon([(54, 6), (58, 6), (57, 7), (54, 7)], outline=color[1]))
wing_polys.append(Polygon([(54, 9), (56, 9), (55, 10), (54, 10)], outline=color[1]))

for wing_poly in wing_polys:
    group.append(wing_poly)


def redraw_wings(index):  # to change colors
    for wing in wing_polys:
        wing.outline = color[index]


# --- Content Setup ---
deco_font = bitmap_font.load_font("/BellotaText-Bold-21.bdf")

# Create two lines of text. Besides changing the text, you can also
# customize the color and font (using Adafruit_CircuitPython_Bitmap_Font).

# text positions
on_x = 15
on_y = 9
off_x = 12
off_y = 9
air_x = 15
air_y = 25


text_line1 = adafruit_display_text.label.Label(deco_font, color=color[3], text="OFF")
text_line1.x = off_x
text_line1.y = off_y

text_line2 = adafruit_display_text.label.Label(deco_font, color=color[1], text="AIR")
text_line2.x = air_x
text_line2.y = air_y

# Put each line of text into the Group
group.append(text_line1)
group.append(text_line2)


def startup_text():
    text_line1.text = "ADA"
    text_line1.x = 10
    text_line1.color = color[2]
    text_line2.text = "FRUIT"
    text_line2.x = 2
    text_line2.color = color[2]
    redraw_wings(0)
    display.show(group)


startup_text()  # display the startup text


def update_text(state):
    if state:  # if switch is on, text is "ON" at startup
        text_line1.text = "ON"
        text_line1.x = on_x
        text_line1.color = color[1]
        text_line2.text = "AIR"
        text_line2.x = air_x
        text_line2.color = color[1]
        redraw_wings(1)
        redraw_frame()
        display.show(group)
    else:  # else, text if "OFF" at startup
        text_line1.text = "OFF"
        text_line1.x = off_x
        text_line1.color = color[3]
        text_line2.text = "AIR"
        text_line2.x = air_x
        text_line2.color = color[3]
        redraw_wings(3)
        redraw_frame()
        display.show(group)


def get_status():
    """
    Get the status whether we are on/off air within operating hours
    If outside of hours, return False
    """
    # Get the time values we need
    now = time.localtime()
    start_hour, start_minute = OPERATING_TIME_START.split(":")
    end_hour, end_minute = OPERATING_TIME_END.split(":")

    # Convert time into a float for easy calculations
    start_time = int(start_hour) + (int(start_minute) / 60)
    end_time = int(end_hour) + (int(end_minute) / 60)
    current_time = now[3] + (now[4] / 60)

    if start_time <= current_time < end_time:
        try:
            on_air = network.fetch_data(DATA_SOURCE, json_path=(DATA_LOCATION1,))
            if len(on_air) > 0:
                return True
        except RuntimeError:
            return False

    return False


# Synchronize Board's clock to Internet
network.get_local_time()
mode_state = get_status()
update_text(mode_state)
last_check = None


while True:
    if last_check is None or time.monotonic() > last_check + UPDATE_DELAY:
        try:
            status = get_status()
            if status:
                if mode_state == 0:  # state has changed, toggle it
                    update_text(1)
                    mode_state = 1
            else:
                if mode_state == 1:
                    update_text(0)
                    mode_state = 0
            print("On Air:", status)
            last_check = time.monotonic()
        except RuntimeError as e:
            print("Some error occured, retrying! -", e)

Now, your sign will connect to your WiFi, connect to Adafruit IO to get the time, and query YouTube for the on air status. If the status is "false" the words "OFF AIR" are displayed. When the stream goes live, the sign will read "ON AIR"!

This guide was first published on Aug 11, 2020. It was last updated on Jul 20, 2020.

This page (Code the On Air Sign) was last updated on May 31, 2023.

Text editor powered by tinymce.