Learning to program is like having a superpower that enables you to create something out of nothing with just a few lines of code. -- ChatGPT
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.
Download the Project Bundle
Your project will use a specific set of CircuitPython libraries and the code.py file. To get everything you need, click on the Download Project Bundle link below, and uncompress the .zip file.
Hook your Pico W to your computer via a known good USB data+power cable. It should show up as a thumb drive named CIRCUITPY.
Using File Explorer/Finder (depending on your Operating System), drag the contents of the uncompressed bundle directory onto your board's CIRCUITPY drive, replacing any existing files or directories with the same names, and adding any new ones that are necessary.
Once the code restarts, it will connect to WiFi and start using OpenAI to generate text according to the default prompt, "Write 1 sentence starting "you can" about an unconventional but useful superpower". The code uses OpenAI's "streaming" mode, so the response appears by chunks, also known as tokens.
Head on to the next pages for advice on how to modify it with your own original prompts as well as explanation of key parts of the code.
# SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
# SPDX-License-Identifier: MIT
import json
import os
import ssl
import traceback
import board
import displayio
import i2cdisplaybus
import digitalio
import keypad
import socketpool
import supervisor
from wifi import radio
import adafruit_requests
import adafruit_displayio_ssd1306
from adafruit_bitmap_font.bitmap_font import load_font
from adafruit_display_text import wrap_text_to_pixels
from adafruit_display_text.bitmap_label import Label
from adafruit_ticks import ticks_add, ticks_less, ticks_ms
# Choose your own prompt and wait messages, either by changing it below inside
# the """triple quoted""" string, or by putting it in your settings.toml file,
# like so:
#
# MY_PROMPT="Give me an idea for a gluten free, keto dinner. Write one sentence"
# PLEASE_WAIT="Cooking something up just for you"
#
# Experimentation is best to figure out what works. Usually you'll want to ask
# for just one sentence or paragraph, since the 128x32 pixel screen can't hold
# much text!
# Here are some that the author found worked reasonably well:
# Give me an idea for a plant-based dinner. Write one sentence
#
# Give jepler (they/them) a cliched and flowery description as a comic book
# supervillain. write one sentence.
#
# Invent and describe an alien species. write one sentence
#
# Invent a zany 'as seen on' product that can't possibly work. One sentence
#
# Tell a 1-sentence story about a kitten and a funny mishap
#
# Make up a 1-sentence fortune for me
#
# In first person, write a 1-sentence story about an AI avoiding boredom in a creative way.
#
# Pick an everyday object (don't say what it is) and describe it using only the
# ten hundred most common words.
#
# Invent an alien animal or plant, name it, and vividly describe it in 1
# sentence
#
# Invent and vividly describe an alien species. write one paragraph
prompt=os.getenv("MY_PROMPT", """
Write 1 sentence starting "you can" about an unconventional but useful superpower
""").strip()
please_wait=os.getenv("PLEASE_WAIT", """
Finding superpower
""").strip()
openai_api_key = os.getenv("OPENAI_API_KEY")
nice_font = load_font("helvR08.pcf")
line_spacing = 9 # in pixels
# i2c display setup
displayio.release_displays()
oled_reset = board.GP9
# STEMMA I2C on picowbell
i2c = board.STEMMA_I2C()
display_bus = i2cdisplaybus.I2CDisplayBus(i2c, device_address=0x3D, reset=oled_reset)
WIDTH = 128
HEIGHT = 64
display = adafruit_displayio_ssd1306.SSD1306(
display_bus, width=WIDTH, height=HEIGHT
)
if openai_api_key is None:
input("Place your\nOPENAI_API_KEY\nin settings.toml")
display.auto_refresh = False
class WrappedTextDisplay(displayio.Group):
def __init__(self):
super().__init__()
self.offset = 0
self.max_lines = display.height // line_spacing
for i in range(self.max_lines):
self.make_label("", i * line_spacing)
self.lines = [""]
self.text = ""
def make_label(self, text, y):
result = Label(
font=nice_font,
color=0xFFFFFF,
background_color=0,
line_spacing=line_spacing,
anchor_point=(0, 0),
anchored_position=(0, y),
text=text)
self.append(result)
def add_text(self, new_text):
print(end=new_text)
if self.lines:
text = self.lines[-1] + new_text
else:
text = new_text
self.lines[-1:] = wrap_text_to_pixels(text, display.width, nice_font)
self.scroll_to_end()
def set_text(self, text):
print("\n\n", end=text)
self.text = text
self.lines = wrap_text_to_pixels(text, display.width, nice_font)
self.offset = 0
def show(self, text):
self.set_text(text)
self.refresh()
def add_show(self, new_text):
self.add_text(new_text)
self.refresh()
def scroll_to_end(self):
self.offset = self.max_offset()
def scroll_next_line(self):
max_offset = self.max_offset()
self.offset = (self.offset + 1) % (max_offset + 1)
def max_offset(self):
return max(0, len(self.lines) - self.max_lines)
def on_last_line(self):
return self.offset == self.max_offset()
def refresh(self):
lines = self.lines
# update labels from wrapped text, accounting for scroll offset
for i in range(len(self)):
offset_i = i + self.offset
if offset_i >= len(lines):
text = ""
else:
text = lines[offset_i]
if text != self[i].text:
self[i].text = text
# Actually update the display all at once
display.refresh()
display.root_group = wrapped_text = WrappedTextDisplay()
def wait_button_scroll_text():
led.switch_to_output(True)
keys.events.clear()
deadline = ticks_add(ticks_ms(),
5000 if wrapped_text.on_last_line() else 1000)
while True:
if (event := keys.events.get()) and event.pressed:
break
if wrapped_text.max_offset() > 0 and ticks_less(deadline, ticks_ms()):
wrapped_text.scroll_next_line()
wrapped_text.refresh()
deadline = ticks_add(deadline,
5000 if wrapped_text.on_last_line() else 1000)
led.value = False
if radio.ipv4_address is None:
wrapped_text.show(f"connecting to {os.getenv('WIFI_SSID')}")
radio.connect(os.getenv('WIFI_SSID'), os.getenv('WIFI_PASSWORD'))
requests = adafruit_requests.Session(socketpool.SocketPool(radio), ssl.create_default_context())
def iter_lines(resp):
partial_line = []
for c in resp.iter_content():
if c == b'\n':
yield (b"".join(partial_line)).decode('utf-8')
del partial_line[:]
else:
partial_line.append(c)
if partial_line:
yield (b"".join(partial_line)).decode('utf-8')
full_prompt = [
{"role": "user", "content": prompt},
]
keys = keypad.Keys((board.GP14,), value_when_pressed=False)
led = digitalio.DigitalInOut(board.GP10)
led.switch_to_output(False)
try:
while True:
wrapped_text.show(please_wait)
with requests.post("https://api.openai.com/v1/chat/completions",
json={"model": "gpt-3.5-turbo", "messages": full_prompt, "stream": True},
headers={
"Authorization": f"Bearer {openai_api_key}",
},
) as response:
wrapped_text.set_text("")
if response.status_code != 200:
wrapped_text.show(f"Uh oh! {response.status_code}: {response.reason}")
else:
wrapped_text.show("")
for line in iter_lines(response):
led.switch_to_output(True)
if line.startswith("data: [DONE]"):
break
if line.startswith("data:"):
content = json.loads(line[5:])
try:
token = content['choices'][0]['delta'].get('content', '')
except (KeyError, IndexError) as e:
token = None
led.value = False
if token:
wrapped_text.add_show(token)
wait_button_scroll_text()
except Exception as e: # pylint: disable=broad-except
traceback.print_exception(e) # pylint: disable=no-value-for-parameter
print(end="\n\n\nAn error occurred\n\nPress button\nto reload")
display.root_group = displayio.CIRCUITPYTHON_TERMINAL
display.auto_refresh = True
while True:
if (event1 := keys.events.get()) and event1.pressed:
break
supervisor.reload()
Page last edited January 21, 2025
Text editor powered by tinymce.