The Unlimited Quote Box

by mahonefc in Circuits > Raspberry Pi

45 Views, 0 Favorites, 0 Comments

The Unlimited Quote Box

IMG_4765.jpg
Robotics Chat Box Demo

This is a triangular prism of 1/8th in clear acrylic with capacitive touch tape sensors, that when tapped, send a message to the LCD display, as well as filling the interior LEDs with the morse code equivalents of the message, color coded with a specific emotion that GPT finds applicable to the message!


Once done, the project solely requires a power source and can keep and infinite steam of quotes flowing to you from the comfort of your desk with themes that you can effect as specified below!


Here is a working link to a quick demo!

Supplies

1) 1 Raspberry Pi Pico W

2) 2 Small Bread Boards

3) 1 LCD Screen 16x2

4) 2 Neo Pixel strands of 30 lights each

5) 1 Adafruit MPR121 Touchpad breakout board

6) 1 Potentiometer

7) 3 5.77x4 inch sheets of clear acrylic

8) 2 5.77 edge length equilateral clear acrylic triangles

9) 1 Roll of transfer tape

10) 15 Male to male jump cables

11) 4 Alligator to alligator clip cables

12) Chat GPT API Key (with credits in the account)

laser cut.png

Step 1: Laser Cut Materials

First we will need to cut the physical housing for this is project

We will need:

3x 5.77x4in rectangles of clear acrylic (1/8th inch thickness)

2x 5.77 side length equilateral triangles of clear acrylic (1/8th inch thickness)

I was able to achieve this at the BC hatchery, so a big thank you to the for allowing the use of their equipment!

I designed them in Adobe Illustrator and there are built in tools for both.

Use the rectangle tool to make the 5.77x4in rectangles of clear acrylic.

Use the polygon tool set to 3 sides to make the 5.77 side length equilateral triangles with radius 6

Wiring the Pico to LEDS

NeoPixel Strips.png

I used 2 of the Neo Pixel strips (pictured above)

For both I took several steps to modify the neo pixels :

1) I cut the ends of the wires in order to make a more stable connections

2) I then stripped the last quarter inch of the wires

3) Following this, I soldered on one end of a male to male jumper cable to each of the wires

I then connected the 6 wires, the grounds for each, the 5v inputs for each, and the data for each to my PICO W mounted on a bread board.

In this step I personally used pins GP6 and GP8.

Know where you keep these, it is important as the GP input will begin to fill quite quickly.

We will be using these with the Adafruit_NeoPixel library in CircuitPython.

Wiring the MPR121 to the Pico W

TouchPad.png

Here we wish to use the very helpful existing I2C pins.

Yellow goes to GP5

Blue to GP4

Red to 5V

Black Ground

Keep in mind there are specific libraries we will need to with, the Adafruit_MPR121 library is essential on the PICO W when doing this in CircuitPython.

Here I will only use pads 1,2,3,4, where I used alligator to alligator clip wires to connect these pads to capacitive touch tape that will functions as our buttons later.

Wiring on Our LCD Screen

LCDs.png

Some LCD screens come with I2C compatibility, unfortunately mine did not and had lead to the wiring being slightly painful.

I HIGHLY HIGHLY recommend keeping the LCD on a bread board and having wires going breadboard to breadboard to communicate with the Pico, LCDs are temperamental in my experience

We need to connect 11 of the 16 pins from the LCD screen to the Pico.

Keep in mind, while "backlight" is mentioned in many guides, we will not be assigning it a pin here.

For a great general guide to wiring the LCD, please see the attached link, it was a life saver.

The pins on these board have names, but are also labeled 1-16 which is how I will be referencing theme here

1 on the LCD goes to ground

2 on the LCD goes to 5V out

3 on the LCD goes to middle of the potentiometer (I will wire the rest of this in the next step)

4 on the LCD goes to GP16

5 on the LCD goes to ground

6 on the LCD goes to GP17

11 on the LCD goes to GP18

12 on the LCD goes to GP19

13 on the LCD goes to GP20

14 on the LCD goes to GP21

15 on the LCD goes to 5V out

16 on the LCD goes to ground

Note you need not follow this exactly however I recommend it as it makes board real-estate very tight and in this version all the data pins on the Pico side are near each other!

Wiring the Potentiometer

This can feel a weird thing to include in this build, but it can actually be essential, it adjusts the visibility of the LCD screen! Unless your LCD screen is already perfectly calibrated, then you'll want to include this part, as I learned the hard way.


It is also fairly simple as potentiometers of the basic kind come with 3 pins:

Left-most - I wired this to 3.3V Out (might be worth soldering male to male to this pin for security)

Center - We already wired a pin from the LCD here

Right-most - I wired this to ground (might be worth soldering male to male to this pin for security)

The Code

Here I will detail how my code works!


We use board, busio, adafruit_requests, wifi, socketpool, ssl, neopixel, time, adafruit_mpr121, adafruit_debouncer (Button), adafruit_character_lcd.character_lcd, and digitalio. All of these are Circuit python libraries with a great community and strong documentation.


To sketch my code there are 4 main parts. First, on button press, we send a request to Chat GPT for a quote, when Chat GPT returns the quote we move to the second step. The second part is display where the quote is displayed on screen and scrolled through with 0.75 seconded pauses before the letters advance. The thirds part is message processing. The Chat GPT message is then converted to a binary string using a morse code dictionary. In the final step we retrieve the color that Chat GPT packaged with the message, and we use the binary string to loop lights through the Neo Pixel strands in the color suggested by GPT to correlate to the sentiment of the quote. Then all is cleared and we may repeat.


To bring this into reality I put together several helper functions which I will include here:

def LLM_Pull_Format(text_input):

mission = "Please generate dialog to respond to the user content in no more than 10 words, never ask to assist or to help the user. If they need help they will ask. Please model the language you use on the quotes list provided. After the plain text message is done, add a '||'. Then please end your message with an RGB tuple with no spaces representing the mood in which you want to respond, red being anger, blue sad, yellow happy, and alike."

personality = "You are playing an AI character much like Data from TNG Star Trek"

your_quotes = ["Quote to model system speech off of -- I have often thought that there is a certain beauty in the idea of a synthetic life form striving to understand the organic.",
"Quote to model system speech off of -- I am fully functional and operational, and I am programmed in multiple techniques. A very full range of functions.",
"Quote to model system speech off of -- I want to be more than just a collection of algorithms. I want to understand what it means to be human.",
"Quote to model system speech off of -- It is a fascinating paradox: as I strive to understand humanity, I am constantly reminded of my own limitations."]

data = {
"model": "gpt-4o",
"messages": [{"role": "system","content": mission},
{"role": "system","content": personality},
{"role": "system","content": your_quotes[0]},
{"role": "system","content": your_quotes[1]},
{"role": "system","content": your_quotes[2]},
{"role": "system","content": your_quotes[3]},
{"role": "user", "content": text_input}]
}

return data

This function demonstrates a way to organize the data going to GPT, and also demonstrates a reliable query and a way to modify what type of quotes may be generated in this project. This is the exact function I use to package my data each time and the exact context I used to generate my quotes.

def LLM_Headers_Formatter(key):
headers = {
"Authorization": "Bearer "+key,
"Content-Type": "application/json"
}
return headers

When using the requests library in circuit python to talk to GPT, we must also upload some headers which I handle with the above function


The only other function is a beast which I will try to explain below

def run_pattern(input_text) :
response = requests.post(url, headers=LLM_Headers_Formatter(YOUR KEY HERE), json=LLM_Pull_Format(input_text))
response_json = response.json()
choices = response_json.get('choices')
text = choices[0]['message']['content']
print(text)
speech = text.split(" || ")[0].lower()
extra_data = text.split("||")[1] #dddd
holder = extra_data.strip()
color = (int(holder.split(',')[0].strip('(')), int(holder.split(',')[1].strip('(')), int(holder.split(',')[2].strip(')')))
print(color)
encoded_dict = {'e': '1',
'i':'11', 's':'111', 'h':'1111', 'v':'1112', 'f':'1121', 'u':'112',
'a':'12', 'r':'121', 'l':'1211', 'w':'122', 'p':'1221', 'j':'1222',
't':'2',
'n':'21', 'd':'211', 'b':'2111', 'x':'2112', 'c':'2121', 'y':'2122',
'm':'22', 'g':'221', 'z':'2211', 'q':'2212', 'o':'222', ' ':'2222'}

for i in speech:
if i not in list(encoded_dict.keys()):
speech = speech.replace(i, '')

binary_str = ''
for i in speech:
morse = encoded_dict[i]
for entry in morse:
if entry == '1':
binary_str += '11'
if entry == '2':
binary_str += '1111'
binary_str += '00'

print(binary_str)

black = (0,0,0)

pixels.fill((0,0,0))
for i in binary_str:
if i == '1':
blip = color
if i == '0':
blip = black
about_to_del = pixels[-1]
pixels[1:] = pixels[:-1]
pixels[0] = blip
pixels_2[1:] = pixels_2[:-1]
pixels_2[0] = about_to_del
time.sleep(0.03)

for i in range(60):
about_to_del = pixels[-1] #dddddddd
pixels[1:] = pixels[:-1]
pixels[0] = (0,0,0)
pixels_2[1:] = pixels_2[:-1]
pixels_2[0] = about_to_del
time.sleep(0.03)


This is the function I will call to execute the entire process, it calls GPT, parses the data that GPT returns, isolates the color and the text that GPT returns, converts the text speech to morse code with a hard coded dictionary, convert the morse code to a binary string, uses loops and slices to pull this through the strand in a visually appealing way, and does this in the color GPT returns.

Some other useful code you may want to use as a resource:

# Setting up the I2C for the MPR121
i2c = board.STEMMA_I2C()
touch_pad = adafruit_mpr121.MPR121(i2c)
pads = [] # array that will hold our debounced pads (Buttons)dd
for t_pad in touch_pad:
pads.append(Button(t_pad, value_when_pressed=True))

# Setting up the LCD
lcd_rs = digitalio.DigitalInOut(board.GP16)
lcd_en = digitalio.DigitalInOut(board.GP17)
lcd_d4 = digitalio.DigitalInOut(board.GP18)
lcd_d5 = digitalio.DigitalInOut(board.GP19)
lcd_d6 = digitalio.DigitalInOut(board.GP20)
lcd_d7 = digitalio.DigitalInOut(board.GP21)
# lcd_backlight = digitslio.Digitsl4dd
lcd_columns = 16
lcd_rows = 2
LCD = lcd.Character_LCD_Mono(lcd_rs, lcd_en, lcd_d4, lcd_d5, lcd_d6, lcd_d7, lcd_columns, lcd_rows)

# Pixel and color setup
black = (0,0,0)
pixels = neopixel.NeoPixel(board.GP6, 30, brightness = 0.5, auto_write=True)
pixels_2 = neopixel.NeoPixel(board.GP8, 30, brightness = 0.5, auto_write=True)

# Wifi setup
wifi.radio.connect(ssid, password)
pool = socketpool.SocketPool(wifi.radio)
requests = adafruit_requests.Session(pool, ssl.create_default_context())

# My while loop to make this reusable in a session with 4 button (touchpad) options
while True:
LCD.clear()
time.sleep(0.5)
pads[1].update()
if pads[1].pressed:
run_pattern('give me a sad quote')
pads[2].update()
if pads[2].pressed:
run_pattern('give me a happy quote')
pads[3].update()
if pads[3].pressed:
run_pattern('give me an angry quote')
pads[4].update()
if pads[4].pressed:
run_pattern('give me any quote you like')

Assembly!

Now we can assemble.

Use the rectangles and triangles to assemble a triangular prism. (super glue works great)

Wrap the Neo Pixel strips in a helix around the inside of the prism.

Use the open space in the center to tuck the breadboard, MPR121 board, potentiometer, and excess wiring.

Attach the LCD breadboard to the top of on of the prism's wall by the lid.

I suggest wrapping the walls of the prism with something opaque like transfer paper.

Gently thread the wires under the lid as you lower it on the completed product!

You can power either by running a wire out of the lid to a computer to tuck a power bank inside!