1
#if !defined(ARDUINO_ESP32_DEV) && !defined(ARDUINO_INKPLATE6V2)
2
#error "Wrong board selection for this example, please select e-radionica Inkplate6 or Soldered Inkplate6 in the boards menu."
3
#endif
4
5
#include <Inkplate.h>
6
#include <WiFi.h>
7
8
#include "./creds.h"
9
10
Inkplate display(INKPLATE_1BIT);
11
WiFiServer server(PORT);
12
13
const char CONNECTING[] = "Connecting WiFi~";
14
const char CONNECT_FAILED[] = "WiFi fail :<";
15
const char SYNCING_NTP[] = "Syncing NTP!";
16
17
enum struct RequestKind {
18
UNKNOWN,
19
SUENA,
20
NO_SUENA,
21
};
22
23
static struct {
24
RequestKind kind;
25
std::string title;
26
std::string artist;
27
std::string album;
28
} request;
29
30
31
// changes according to font size!
32
#define MAX_LINE_LEN 18
33
34
static uint16_t sizeUp(std::string const& text)
35
{
36
uint16_t lines = 1,
37
ix = 0,
38
remaining = text.size();
39
40
while (remaining > MAX_LINE_LEN) {
41
// look for a space to break at between [ix, ix+MLL)
42
int16_t s = ix + MAX_LINE_LEN - 1;
43
bool found = false;
44
for (; s >= ix; --s) {
45
if (text[s] == ' ') {
46
++lines;
47
ix = s + 1;
48
remaining = text.size() - ix;
49
found = true;
50
break;
51
}
52
}
53
if (!found) {
54
// just break at MLL then
55
++lines;
56
ix += MAX_LINE_LEN;
57
remaining -= MAX_LINE_LEN;
58
}
59
}
60
return lines;
61
}
62
63
static void displaySizedUp(std::string& text, uint16_t& y)
64
{
65
uint8_t lines = sizeUp(text);
66
67
int16_t _x, _y;
68
uint16_t w, h;
69
uint16_t ix = 0,
70
remaining = text.size();
71
72
while (remaining > MAX_LINE_LEN) {
73
// look for a space to break at between [ix, ix+MLL)
74
int16_t s = ix + MAX_LINE_LEN - 1;
75
bool found = false;
76
for (; s >= ix; --s) {
77
if (text[s] == ' ') {
78
auto substr = text.substr(ix, s - ix);
79
while (substr[substr.length() - 1] == ' ')
80
substr.pop_back();
81
display.getTextBounds(substr.c_str(), 0, 0, &_x, &_y, &w, &h);
82
display.setCursor((display.width() - w) / 2, y);
83
display.print(substr.c_str());
84
y += h;
85
ix = s + 1;
86
remaining = text.size() - ix;
87
found = true;
88
break;
89
}
90
}
91
if (!found) {
92
// just break at MLL then
93
auto substr = text.substr(ix, MAX_LINE_LEN);
94
while (substr[substr.length() - 1] == ' ')
95
substr.pop_back();
96
display.getTextBounds(substr.c_str(), 0, 0, &_x, &_y, &w, &h);
97
display.setCursor((display.width() - w) / 2, y);
98
display.print(substr.c_str());
99
y += h;
100
ix += MAX_LINE_LEN;
101
remaining -= MAX_LINE_LEN;
102
}
103
}
104
105
if (remaining > 0) {
106
auto substr = text.substr(ix);
107
while (substr[substr.length() - 1] == ' ')
108
substr.pop_back();
109
display.getTextBounds(substr.c_str(), 0, 0, &_x, &_y, &w, &h);
110
display.setCursor((display.width() - w) / 2, y);
111
display.print(substr.c_str());
112
y += h;
113
}
114
}
115
116
static void displayTextCenteredThirds(std::string& a, std::string& b, std::string& c)
117
{
118
auto a_sz = sizeUp(a);
119
auto b_sz = sizeUp(b);
120
auto c_sz = sizeUp(c);
121
122
// determine font height.
123
int16_t _x, _y;
124
uint16_t w, h;
125
display.getTextBounds("A", 0, 0, &_x, &_y, &w, &h);
126
127
128
uint16_t i = 35;
129
uint16_t centre_y = display.height() / 2;
130
uint16_t y = centre_y - (h * (a_sz + b_sz + c_sz) + i * 2) / 2;
131
132
displaySizedUp(a, y);
133
y += i;
134
displaySizedUp(b, y);
135
y += i;
136
displaySizedUp(c, y);
137
}
138
139
static void displayTextCenteredThird(char const *text, int third)
140
{
141
uint16_t centre_h = display.height() / 3;
142
centre_h = centre_h * third + centre_h / 2;
143
144
int16_t _x, _y;
145
uint16_t w, h;
146
display.getTextBounds(text, 0, 0, &_x, &_y, &w, &h);
147
148
display.setCursor((display.width() - w) / 2, centre_h - h / 2);
149
display.print(text);
150
}
151
152
// static void displayTextRightLn(char const* text)
153
// {
154
// int16_t x, y;
155
// uint16_t w, h;
156
// display.getTextBounds(text, 0, 0, &x, &y, &w, &h);
157
// display.setCursor(display.width() - w, display.getCursorY());
158
// display.println(text);
159
// }
160
161
static void refresh()
162
{
163
display.clearDisplay();
164
display.setTextSize(7);
165
if (request.kind == RequestKind::SUENA) {
166
displayTextCenteredThirds(
167
request.title,
168
request.artist,
169
request.album);
170
} else {
171
displayTextCenteredThird("no esta sonando", 1);
172
}
173
display.display();
174
}
175
176
static void connectWifi()
177
{
178
display.clearDisplay();
179
displayTextCenteredThird(CONNECTING, 1);
180
display.display();
181
182
WiFi.mode(WIFI_STA);
183
WiFi.setHostname(HOSTNAME);
184
bool attempted = false;
185
186
while (true) {
187
int status = WiFi.status();
188
if (status == WL_CONNECTED) {
189
break;
190
} else if (!attempted) {
191
WiFi.begin(WIFI_SSID, WIFI_PASS);
192
attempted = true;
193
} else if (status == WL_CONNECT_FAILED) {
194
display.clearDisplay();
195
displayTextCenteredThird(CONNECT_FAILED, 1);
196
display.display();
197
delay(1000);
198
WiFi.begin(WIFI_SSID, WIFI_PASS);
199
}
200
delay(500);
201
}
202
203
server.begin();
204
}
205
206
void setup()
207
{
208
display.begin();
209
display.setTextColor(1, 0);
210
display.setTextWrap(false);
211
display.setTextSize(7);
212
213
connectWifi();
214
215
refresh();
216
}
217
218
#define URI_SUENA "/suena"
219
#define URI_NO_SUENA "/no_suena"
220
221
#define X_TITLE "x-title: "
222
#define X_ARTIST "x-artist: "
223
#define X_ALBUM "x-album: "
224
225
static bool parseClientRequest(WiFiClient& client)
226
{
227
enum {
228
VERB, // "POST"
229
URI, // "/SECRET/suena"
230
HTTP, // "HTTP/1.1"
231
HEADERS, // etc.
232
} parse_state = VERB;
233
char buf[255];
234
uint8_t len = 0;
235
uint8_t parcels = 0;
236
request.kind = RequestKind::UNKNOWN;
237
238
unsigned long start = millis();
239
240
while (client.connected()) {
241
if (!client.available()) {
242
if (millis() - start > 5000) {
243
return false;
244
}
245
delay(10);
246
continue;
247
}
248
char c = client.read();
249
switch (parse_state) {
250
case VERB:
251
if (c >= 'A' && c <= 'Z' && len < 254) {
252
buf[len++] = c;
253
} else if (c == ' ') {
254
buf[len] = 0;
255
if (strcmp(buf, "POST") != 0) {
256
return false;
257
}
258
parse_state = URI;
259
len = 0;
260
} else {
261
return false;
262
}
263
break;
264
case URI:
265
if ((c == '/'
266
|| c == '_'
267
|| (c >= 'a' && c <= 'z')
268
|| (c >= '0' && c <= '9'))
269
&& len < 254) {
270
buf[len++] = c;
271
} else if (c == ' ') {
272
buf[len] = 0;
273
if (len < (sizeof(SECRET) - 1) + 1) {
274
return false;
275
}
276
if (strncmp(buf, "/" SECRET, sizeof(SECRET) - 1 + 1) != 0) {
277
return false;
278
}
279
if (strcmp(&buf[sizeof(SECRET) - 1 + 1], URI_SUENA) == 0) {
280
request.kind = RequestKind::SUENA;
281
} else if (strcmp(&buf[sizeof(SECRET) - 1 + 1], URI_NO_SUENA) == 0) {
282
request.kind = RequestKind::NO_SUENA;
283
} else {
284
return false;
285
}
286
parse_state = HTTP;
287
len = 0;
288
} else {
289
return false;
290
}
291
break;
292
case HTTP:
293
if ((c == '/'
294
|| c == '1'
295
|| c == '.'
296
|| c == '0'
297
|| (c >= 'A' && c <= 'Z'))
298
&& len < 254) {
299
buf[len++] = c;
300
} else if (c == '\r') {
301
buf[len] = 0;
302
if (strcmp(buf, "HTTP/1.0") == 0
303
|| strcmp(buf, "HTTP/1.1") == 0) {
304
// ok cool
305
} else {
306
// ok not cool
307
return false;
308
}
309
parse_state = HEADERS;
310
len = 1; // HEADERS loop not to think we're done when it sees \n
311
} else {
312
return false;
313
}
314
break;
315
case HEADERS:
316
if (c == '\n' && len == 0) {
317
// empty line, request fish
318
return (request.kind == RequestKind::SUENA && parcels == 3) ||
319
(request.kind == RequestKind::NO_SUENA && parcels == 0);
320
} else if (c == '\n') {
321
len = 0;
322
} else if (c == '\r') {
323
// process line if any
324
if (len > sizeof(X_TITLE) - 1 && strncmp(buf, X_TITLE, sizeof(X_TITLE) - 1) == 0) {
325
request.title = std::string(buf + sizeof(X_TITLE) - 1, len - sizeof(X_TITLE) + 1);
326
++parcels;
327
} else if (len > sizeof(X_ARTIST) - 1 && strncmp(buf, X_ARTIST, sizeof(X_ARTIST) - 1) == 0) {
328
request.artist = std::string(buf + sizeof(X_ARTIST) - 1, len - sizeof(X_ARTIST) + 1);
329
++parcels;
330
} else if (len > sizeof(X_ALBUM) - 1 && strncmp(buf, X_ALBUM, sizeof(X_ALBUM) - 1) == 0) {
331
request.album = std::string(buf + sizeof(X_ALBUM) - 1, len - sizeof(X_ALBUM) + 1);
332
++parcels;
333
}
334
} else {
335
buf[len] = c;
336
len++;
337
}
338
}
339
}
340
341
return false;
342
}
343
344
void loop()
345
{
346
if (WiFi.status() != WL_CONNECTED) {
347
connectWifi();
348
refresh();
349
}
350
351
WiFiClient client = server.available();
352
if (client) {
353
bool success = false;
354
if (parseClientRequest(client)) {
355
switch (request.kind) {
356
case RequestKind::UNKNOWN:
357
break;
358
case RequestKind::SUENA:
359
success = true;
360
break;
361
case RequestKind::NO_SUENA:
362
success = true;
363
break;
364
}
365
}
366
if (success) {
367
client.println("HTTP/1.1 200 OK");
368
client.println("Content-Type: text/plain");
369
client.println("Connection: close");
370
client.println();
371
client.println("que bella");
372
refresh();
373
} else {
374
client.println("HTTP/1.1 400 Bad Request");
375
client.println("Content-Type: text/plain");
376
client.println("Connection: close");
377
client.println();
378
client.println("yo bro wtf is this");
379
}
380
client.stop();
381
}
382
delay(5);
383
}
384