Skip to content

Commit d8a35f2

Browse files
committed
2 parents de54ad5 + eea188d commit d8a35f2

File tree

4 files changed

+297
-115
lines changed

4 files changed

+297
-115
lines changed

BLE_Client_Server/server/code.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
# SPDX-License-Identifier: MIT
44

55
from time import sleep
6-
from adafruit_ble.uart_server import UARTServer
6+
from adafruit_ble import BLERadio
7+
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
8+
from adafruit_ble.services.nordic import UARTService
79
from adafruit_bluefruit_connect.packet import Packet
810
from adafruit_bluefruit_connect.button_packet import ButtonPacket
911
from adafruit_bluefruit_connect.color_packet import ColorPacket
@@ -17,15 +19,16 @@
1719
solenoid.direction = Direction.OUTPUT
1820
solenoid.value = False
1921

20-
uart_server = UARTServer()
22+
ble = BLERadio()
23+
uart_server = UARTService()
24+
advertisement = ProvideServicesAdvertisement(uart_server)
2125

2226
while True:
23-
uart_server.start_advertising() # Advertise when not connected.
24-
25-
while not uart_server.connected: # Wait for connection
27+
ble.start_advertising(advertisement) # Advertise when not connected.
28+
while not ble.connected:
2629
pass
2730

28-
while uart_server.connected: # Connected
31+
while ble.connected: # Connected
2932
if uart_server.in_waiting: # Check BLE commands
3033
packet = Packet.from_stream(uart_server)
3134
if isinstance(packet, ButtonPacket):
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
# SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
2+
# SPDX-License-Identifier: MIT
3+
import json
4+
import os
5+
import ssl
6+
import traceback
7+
8+
import board
9+
import displayio
10+
import digitalio
11+
import keypad
12+
import socketpool
13+
import supervisor
14+
from wifi import radio
15+
16+
import adafruit_requests
17+
import adafruit_displayio_ssd1306
18+
from adafruit_bitmap_font.bitmap_font import load_font
19+
from adafruit_display_text import wrap_text_to_pixels
20+
from adafruit_display_text.bitmap_label import Label
21+
from adafruit_ticks import ticks_add, ticks_less, ticks_ms
22+
23+
24+
# Choose your own prompt and wait messages, either by changing it below inside
25+
# the """triple quoted""" string, or by putting it in your settings.toml file,
26+
# like so:
27+
#
28+
# MY_PROMPT="Give me an idea for a gluten free, keto dinner. Write one sentence"
29+
# PLEASE_WAIT="Cooking something up just for you"
30+
#
31+
# Experimentation is best to figure out what works. Usually you'll want to ask
32+
# for just one sentence or paragraph, since the 128x32 pixel screen can't hold
33+
# much text!
34+
35+
# Here are some that the author found worked reasonably well:
36+
37+
# Give me an idea for a plant-based dinner. Write one sentence
38+
#
39+
# Give jepler (they/them) a cliched and flowery description as a comic book
40+
# supervillain. write one sentence.
41+
#
42+
# Invent and describe an alien species. write one sentence
43+
#
44+
# Invent a zany 'as seen on' product that can't possibly work. One sentence
45+
#
46+
# Tell a 1-sentence story about a kitten and a funny mishap
47+
#
48+
# Make up a 1-sentence fortune for me
49+
#
50+
# In first person, write a 1-sentence story about an AI avoiding boredom in a creative way.
51+
#
52+
# Pick an everyday object (don't say what it is) and describe it using only the
53+
# ten hundred most common words.
54+
#
55+
# Invent an alien animal or plant, name it, and vividly describe it in 1
56+
# sentence
57+
#
58+
# Invent and vividly describe an alien species. write one paragraph
59+
60+
prompt=os.getenv("MY_PROMPT", """
61+
Write 1 sentence starting "you can" about an unconventional but useful superpower
62+
""").strip()
63+
please_wait=os.getenv("PLEASE_WAIT", """
64+
Finding superpower
65+
""").strip()
66+
67+
openai_api_key = os.getenv("OPENAI_API_KEY")
68+
69+
nice_font = load_font("helvR08.pcf")
70+
line_spacing = 9 # in pixels
71+
72+
# i2c display setup
73+
displayio.release_displays()
74+
oled_reset = board.GP9
75+
76+
# STEMMA I2C on picowbell
77+
i2c = board.STEMMA_I2C()
78+
display_bus = displayio.I2CDisplay(i2c, device_address=0x3D, reset=oled_reset)
79+
80+
WIDTH = 128
81+
HEIGHT = 64
82+
83+
display = adafruit_displayio_ssd1306.SSD1306(
84+
display_bus, width=WIDTH, height=HEIGHT
85+
)
86+
if openai_api_key is None:
87+
input("Place your\nOPENAI_API_KEY\nin settings.toml")
88+
display.auto_refresh = False
89+
90+
class WrappedTextDisplay(displayio.Group):
91+
def __init__(self):
92+
super().__init__()
93+
self.offset = 0
94+
self.max_lines = display.height // line_spacing
95+
for i in range(self.max_lines):
96+
self.make_label("", i * line_spacing)
97+
self.lines = [""]
98+
self.text = ""
99+
100+
def make_label(self, text, y):
101+
result = Label(
102+
font=nice_font,
103+
color=0xFFFFFF,
104+
background_color=0,
105+
line_spacing=line_spacing,
106+
anchor_point=(0, 0),
107+
anchored_position=(0, y),
108+
text=text)
109+
self.append(result)
110+
111+
def add_text(self, new_text):
112+
print(end=new_text)
113+
if self.lines:
114+
text = self.lines[-1] + new_text
115+
else:
116+
text = new_text
117+
self.lines[-1:] = wrap_text_to_pixels(text, display.width, nice_font)
118+
self.scroll_to_end()
119+
120+
def set_text(self, text):
121+
print("\n\n", end=text)
122+
self.text = text
123+
self.lines = wrap_text_to_pixels(text, display.width, nice_font)
124+
self.offset = 0
125+
126+
def show(self, text):
127+
self.set_text(text)
128+
self.refresh()
129+
130+
def add_show(self, new_text):
131+
self.add_text(new_text)
132+
self.refresh()
133+
134+
def scroll_to_end(self):
135+
self.offset = self.max_offset()
136+
137+
def scroll_next_line(self):
138+
max_offset = self.max_offset()
139+
self.offset = (self.offset + 1) % (max_offset + 1)
140+
141+
def max_offset(self):
142+
return max(0, len(self.lines) - self.max_lines)
143+
144+
def on_last_line(self):
145+
return self.offset == self.max_offset()
146+
147+
def refresh(self):
148+
lines = self.lines
149+
# update labels from wrapped text, accounting for scroll offset
150+
for i in range(len(self)):
151+
offset_i = i + self.offset
152+
if offset_i >= len(lines):
153+
text = ""
154+
else:
155+
text = lines[offset_i]
156+
if text != self[i].text:
157+
self[i].text = text
158+
159+
# Actually update the display all at once
160+
display.refresh()
161+
162+
display.root_group = wrapped_text = WrappedTextDisplay()
163+
164+
def wait_button_scroll_text():
165+
led.switch_to_output(True)
166+
keys.events.clear()
167+
deadline = ticks_add(ticks_ms(),
168+
5000 if wrapped_text.on_last_line() else 1000)
169+
while True:
170+
if (event := keys.events.get()) and event.pressed:
171+
break
172+
if wrapped_text.max_offset() > 0 and ticks_less(deadline, ticks_ms()):
173+
wrapped_text.scroll_next_line()
174+
wrapped_text.refresh()
175+
deadline = ticks_add(deadline,
176+
5000 if wrapped_text.on_last_line() else 1000)
177+
led.value = False
178+
179+
if radio.ipv4_address is None:
180+
wrapped_text.show(f"connecting to {os.getenv('WIFI_SSID')}")
181+
radio.connect(os.getenv('WIFI_SSID'), os.getenv('WIFI_PASSWORD'))
182+
requests = adafruit_requests.Session(socketpool.SocketPool(radio), ssl.create_default_context())
183+
184+
def iter_lines(resp):
185+
partial_line = []
186+
for c in resp.iter_content():
187+
if c == b'\n':
188+
yield (b"".join(partial_line)).decode('utf-8')
189+
del partial_line[:]
190+
else:
191+
partial_line.append(c)
192+
if partial_line:
193+
yield (b"".join(partial_line)).decode('utf-8')
194+
195+
full_prompt = [
196+
{"role": "user", "content": prompt},
197+
]
198+
199+
keys = keypad.Keys((board.GP14,), value_when_pressed=False)
200+
led = digitalio.DigitalInOut(board.GP10)
201+
led.switch_to_output(False)
202+
203+
try:
204+
while True:
205+
wrapped_text.show(please_wait)
206+
207+
with requests.post("https://api.openai.com/v1/chat/completions",
208+
json={"model": "gpt-3.5-turbo", "messages": full_prompt, "stream": True},
209+
headers={
210+
"Authorization": f"Bearer {openai_api_key}",
211+
},
212+
) as response:
213+
214+
wrapped_text.set_text("")
215+
if response.status_code != 200:
216+
wrapped_text.show(f"Uh oh! {response.status_code}: {response.reason}")
217+
else:
218+
wrapped_text.show("")
219+
for line in iter_lines(response):
220+
led.switch_to_output(True)
221+
if line.startswith("data: [DONE]"):
222+
break
223+
if line.startswith("data:"):
224+
content = json.loads(line[5:])
225+
try:
226+
token = content['choices'][0]['delta'].get('content', '')
227+
except (KeyError, IndexError) as e:
228+
token = None
229+
led.value = False
230+
if token:
231+
wrapped_text.add_show(token)
232+
wait_button_scroll_text()
233+
except Exception as e: # pylint: disable=broad-except
234+
traceback.print_exception(e) # pylint: disable=no-value-for-parameter
235+
print(end="\n\n\nAn error occurred\n\nPress button\nto reload")
236+
display.root_group = displayio.CIRCUITPYTHON_TERMINAL
237+
display.auto_refresh = True
238+
while True:
239+
if (event1 := keys.events.get()) and event1.pressed:
240+
break
241+
supervisor.reload()
Binary file not shown.

0 commit comments

Comments
 (0)