Skip to content

Commit a061cb2

Browse files
committed
Add OpenAI API prompt generation example
1 parent 0d777fa commit a061cb2

File tree

18 files changed

+6405
-0
lines changed

18 files changed

+6405
-0
lines changed

examples/Inkplate10/Projects/Inkplate10_OpenAI_Text_Prompt/FreeMonoBold18pt7b.h

Lines changed: 424 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
/*
2+
Inkplate10 OpenAI text prompt generator
3+
Compatible with Soldered Inkplate 10
4+
5+
For this example you will need only USB cable and Inkplate 10.
6+
Select "e-radionica Inkplate10" or "Soldered Inkplate10" from Tools -> Board menu.
7+
Don't have "e-radionica Inkplate10" or "Soldered Inkplate10" option? Follow our tutorial and add it:https://soldered.com/documentation/inkplate/6/quick-start-guide/
8+
9+
Overview:
10+
This example demonstrates how to fetch the temperature and weather, then with that information it creates a snarky prompt which is displayed
11+
on the Inkplate
12+
13+
Before You Start:
14+
- Enter your WiFi credentials carefully (they are case-sensitive).
15+
- Update the following variables for accurate local weather data:
16+
• location
17+
• latitude
18+
• longitude
19+
- After creating an OpenAI API key, enter it in the openai_key variable
20+
*/
21+
22+
23+
#include <WiFiClientSecure.h> // Secure WiFi client for HTTPS communication
24+
#include <ArduinoJson.h> // Library for parsing and generating JSON
25+
#include "Inkplate.h" // Inkplate display driver
26+
#include "FreeMonoBold18pt7b.h" // Font used for rendering text on the display
27+
28+
// How long the device will stay in deep sleep (in minutes)
29+
#define SLEEP_DURATION_IN_MINS 30*60
30+
31+
// WiFi credentials
32+
const char* ssid = "YOUR_SSID";
33+
const char* password = "YOUR_PASSWORD";
34+
35+
// OpenAI API key
36+
const char* openai_key = "YOUR_API_KEY_HERE";
37+
38+
// Location and coordinates for weather query
39+
String location = "Osijek";
40+
const float latitude = 45.5600;
41+
const float longitude = 18.6750;
42+
43+
// Variables to hold weather data
44+
float temperature;
45+
String weatherDesc, timeStr;
46+
47+
// HTTPS client instance
48+
WiFiClientSecure client;
49+
50+
// Inkplate display instance (1-bit mode for faster update and lower power usage)
51+
Inkplate display(INKPLATE_1BIT);
52+
53+
void setup() {
54+
display.begin(); // Initialize the Inkplate display
55+
56+
Serial.begin(115200); // Initialize serial for debugging
57+
58+
// Connect to WiFi
59+
display.print("Connecting to WiFi...");
60+
WiFi.begin(ssid, password);
61+
while (WiFi.status() != WL_CONNECTED) {
62+
delay(500);
63+
display.print(".");
64+
display.partialUpdate(); // Refresh only changed parts of display for faster performance
65+
}
66+
67+
display.println("\nConnected!");
68+
display.display(); // Show connection message
69+
display.clearDisplay(); // Clear display for next content
70+
71+
// Get current weather data
72+
if (getWeather(latitude, longitude, temperature, weatherDesc, timeStr)) {
73+
// Build prompt to send to OpenAI
74+
String prompt = "Give me a sarcastic 120-word max summary of the weather in " + location +
75+
". It's currently " + String(temperature, 1) + "C with " + weatherDesc +
76+
" skies at " + timeStr +
77+
" (Just take the Hour and minutes, without AM or PM). Make it witty and slightly condescending. Dont use '—' and ' symbols, use - and ' respectively";
78+
79+
// Get AI-generated snarky response
80+
String snarkySummary = getOpenAIResponse(prompt);
81+
82+
// Display the response on screen
83+
display.drawTextBox(100, 100, 1100, 720, snarkySummary.c_str(), 1, &FreeMonoBold18pt7b, NULL, false, 28);
84+
display.display(); // Push the buffer to the screen
85+
} else {
86+
// Handle weather data fetch failure
87+
display.println("Failed to get weather data.");
88+
display.display();
89+
}
90+
91+
// Set a wakeup alarm 30 seconds from now (RTC-based wakeup)
92+
display.rtcSetAlarmEpoch(display.rtcGetEpoch() + SLEEP_DURATION_IN_MINS, RTC_ALARM_MATCH_DHHMMSS);
93+
94+
// Configure ESP32 to wake up from deep sleep using RTC interrupt on GPIO 39
95+
esp_sleep_enable_ext0_wakeup(GPIO_NUM_39, 0);
96+
97+
// Enter deep sleep to save power
98+
esp_deep_sleep_start();
99+
}
100+
101+
void loop() {
102+
// Empty loop – never runs due to deep sleep
103+
}
104+
105+
/**
106+
* Fetch current weather from Open-Meteo API
107+
*
108+
* @param lat Latitude of the location
109+
* @param lon Longitude of the location
110+
* @param temperature Output: current temperature
111+
* @param weatherDesc Output: human-readable weather description
112+
* @param timeStr Output: current time string
113+
* @return true if successful, false on error
114+
*/
115+
bool getWeather(float lat, float lon, float& temperature, String& weatherDesc, String& timeStr) {
116+
client.setInsecure(); // Skip TLS verification (not recommended for production)
117+
118+
// Construct the API URL
119+
String url = "/v1/forecast?latitude=" + String(lat, 4) + "&longitude=" + String(lon, 4) +
120+
"&current_weather=true&timezone=auto";
121+
122+
// Connect to Open-Meteo API server
123+
if (!client.connect("api.open-meteo.com", 443)) {
124+
Serial.println("Connection to Open-Meteo failed!");
125+
return false;
126+
}
127+
128+
// Send HTTP GET request
129+
client.println("GET " + url + " HTTP/1.1");
130+
client.println("Host: api.open-meteo.com");
131+
client.println("Connection: close");
132+
client.println();
133+
134+
// Wait for response or timeout after 10s
135+
unsigned long timeout = millis();
136+
while (!client.available() && millis() - timeout < 10000) {
137+
delay(100);
138+
}
139+
140+
if (!client.available()) {
141+
Serial.println("Open-Meteo timeout");
142+
return false;
143+
}
144+
145+
// Skip HTTP headers
146+
while (client.connected()) {
147+
String line = client.readStringUntil('\n');
148+
if (line == "\r") break;
149+
}
150+
151+
// Read response body, ignoring chunked encoding markers
152+
String jsonPayload = "";
153+
while (client.available()) {
154+
String line = client.readStringUntil('\n');
155+
line.trim(); // Remove trailing whitespace and \r
156+
157+
// Skip lines that are chunk size indicators in hex
158+
bool isHex = true;
159+
for (size_t i = 0; i < line.length(); i++) {
160+
if (!isHexadecimalDigit(line[i])) {
161+
isHex = false;
162+
break;
163+
}
164+
}
165+
if (isHex) continue;
166+
167+
jsonPayload += line;
168+
}
169+
170+
// Parse JSON payload
171+
DynamicJsonDocument doc(2048);
172+
DeserializationError error = deserializeJson(doc, jsonPayload);
173+
174+
if (error) {
175+
Serial.print("JSON parse error: ");
176+
Serial.println(error.c_str());
177+
return false;
178+
}
179+
180+
// Extract weather info
181+
temperature = doc["current_weather"]["temperature"];
182+
int weatherCode = doc["current_weather"]["weathercode"];
183+
timeStr = doc["current_weather"]["time"].as<String>();
184+
weatherDesc = weatherCodeToString(weatherCode);
185+
186+
return true;
187+
}
188+
189+
/**
190+
* Convert weather code from Open-Meteo into readable string
191+
*
192+
* @param code Weather condition code
193+
* @return String description
194+
*/
195+
String weatherCodeToString(int code) {
196+
switch (code) {
197+
case 0: return "clear";
198+
case 1: return "mostly clear";
199+
case 2: return "partly cloudy";
200+
case 3: return "overcast";
201+
case 45: case 48: return "foggy";
202+
case 51: case 53: case 55: return "drizzling";
203+
case 61: case 63: case 65: return "raining";
204+
case 80: case 81: case 82: return "showery";
205+
default: return "weirdly unclassifiable"; // fallback for unknown codes
206+
}
207+
}
208+
209+
/**
210+
* Send prompt to OpenAI's chat API and return the generated response
211+
*
212+
* @param prompt The question or request to send to OpenAI
213+
* @return OpenAI's textual response, or empty string on error
214+
*/
215+
String getOpenAIResponse(String prompt) {
216+
client.setInsecure(); // Skip TLS cert verification
217+
218+
// Connect to OpenAI API server
219+
if (!client.connect("api.openai.com", 443)) {
220+
Serial.println("Connection to OpenAI failed!");
221+
return "";
222+
}
223+
224+
// Prepare JSON request body
225+
DynamicJsonDocument doc(1024);
226+
doc["model"] = "o3-mini"; // OpenAI chat model
227+
JsonArray messages = doc.createNestedArray("messages");
228+
JsonObject message = messages.createNestedObject();
229+
message["role"] = "user";
230+
message["content"] = prompt;
231+
232+
// Serialize request body to string
233+
String requestBody;
234+
serializeJson(doc, requestBody);
235+
236+
// Send HTTP POST request
237+
Serial.println("Sending request to OpenAI...");
238+
client.println("POST /v1/chat/completions HTTP/1.1");
239+
client.println("Host: api.openai.com");
240+
client.println("Authorization: Bearer " + String(openai_key));
241+
client.println("Content-Type: application/json");
242+
client.print("Content-Length: ");
243+
client.println(requestBody.length());
244+
client.println("Connection: close");
245+
client.println();
246+
client.println(requestBody);
247+
248+
// Wait for response with timeout
249+
unsigned long timeout = millis();
250+
while (!client.available() && millis() - timeout < 30000) {
251+
delay(100);
252+
}
253+
254+
if (!client.available()) {
255+
Serial.println("No response from OpenAI - timeout");
256+
return "";
257+
}
258+
259+
// Skip response headers
260+
while (client.connected()) {
261+
String line = client.readStringUntil('\n');
262+
if (line == "\r") break;
263+
}
264+
265+
// Read and parse the JSON response body
266+
String response = client.readString();
267+
Serial.println("Raw response: " + response); // For debugging
268+
269+
DynamicJsonDocument jsonDoc(2048);
270+
DeserializationError error = deserializeJson(jsonDoc, response);
271+
272+
if (error) {
273+
Serial.print("JSON parsing failed: ");
274+
Serial.println(error.c_str());
275+
return "";
276+
}
277+
278+
// Check and extract reply content
279+
if (!jsonDoc.containsKey("choices")) {
280+
Serial.println("Unexpected response format");
281+
if (jsonDoc.containsKey("error")) {
282+
Serial.println("Error: " + jsonDoc["error"]["message"].as<String>());
283+
}
284+
return "";
285+
}
286+
287+
return jsonDoc["choices"][0]["message"]["content"].as<String>();
288+
}

0 commit comments

Comments
 (0)