We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
suena / suena / suena.ino
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
std::string msg;
29
} request;
30
31
32
// changes according to font size!
33
#define MAX_LINE_LEN 18
34
35
static uint16_t sizeUp(std::string const& text)
36
{
37
uint16_t lines = 1,
38
ix = 0,
39
remaining = text.size();
40
41
if (remaining == 0)
42
return 0;
43
44
while (remaining > MAX_LINE_LEN) {
45
// look for a space to break at between [ix, ix+MLL)
46
int16_t s = ix + MAX_LINE_LEN - 1;
47
bool found = false;
48
for (; s >= ix; --s) {
49
if (text[s] == ' ') {
50
++lines;
51
ix = s + 1;
52
remaining = text.size() - ix;
53
found = true;
54
break;
55
}
56
}
57
if (!found) {
58
// just break at MLL then
59
++lines;
60
ix += MAX_LINE_LEN;
61
remaining -= MAX_LINE_LEN;
62
}
63
}
64
return lines;
65
}
66
67
static void displaySizedUp(std::string const& text, uint16_t& y)
68
{
69
uint8_t lines = sizeUp(text);
70
71
int16_t _x, _y;
72
uint16_t w, h;
73
uint16_t ix = 0,
74
remaining = text.size();
75
76
while (remaining > MAX_LINE_LEN) {
77
// look for a space to break at between [ix, ix+MLL)
78
int16_t s = ix + MAX_LINE_LEN - 1;
79
bool found = false;
80
for (; s >= ix; --s) {
81
if (text[s] == ' ') {
82
auto substr = text.substr(ix, s - ix);
83
while (substr[substr.length() - 1] == ' ')
84
substr.pop_back();
85
display.getTextBounds(substr.c_str(), 0, 0, &_x, &_y, &w, &h);
86
display.setCursor((display.width() - w) / 2, y);
87
display.print(substr.c_str());
88
y += h + 1;
89
ix = s + 1;
90
remaining = text.size() - ix;
91
found = true;
92
break;
93
}
94
}
95
if (!found) {
96
// just break at MLL then
97
auto substr = text.substr(ix, MAX_LINE_LEN);
98
while (substr[substr.length() - 1] == ' ')
99
substr.pop_back();
100
display.getTextBounds(substr.c_str(), 0, 0, &_x, &_y, &w, &h);
101
display.setCursor((display.width() - w) / 2, y);
102
display.print(substr.c_str());
103
y += h + 1;
104
ix += MAX_LINE_LEN;
105
remaining -= MAX_LINE_LEN;
106
}
107
}
108
109
if (remaining > 0) {
110
auto substr = text.substr(ix);
111
while (substr[substr.length() - 1] == ' ')
112
substr.pop_back();
113
display.getTextBounds(substr.c_str(), 0, 0, &_x, &_y, &w, &h);
114
display.setCursor((display.width() - w) / 2, y);
115
display.print(substr.c_str());
116
y += h;
117
}
118
}
119
120
const uint16_t DASH_WIDTH = 50;
121
122
static void displayTextCenteredThirds(std::string const& a, std::string const& b, std::string const& c)
123
{
124
auto a_sz = sizeUp(a);
125
auto b_sz = sizeUp(b);
126
auto c_sz = sizeUp(c);
127
128
// determine font height.
129
int16_t _x, _y;
130
uint16_t w, h;
131
display.getTextBounds("A", 0, 0, &_x, &_y, &w, &h);
132
133
134
uint16_t i = 88;
135
uint16_t centre_y = display.height() / 2;
136
uint16_t y = centre_y - ((h + 1) * (a_sz + b_sz + c_sz) + i * 2) / 2;
137
138
displaySizedUp(a, y);
139
y += i/2 - 1;
140
if (a_sz != 0 && b_sz != 0)
141
display.drawThickLine(display.width() / 2 - DASH_WIDTH / 2, y, display.width() / 2 + DASH_WIDTH / 2, y, 1, 3);
142
y += i/2 + 2;
143
displaySizedUp(b, y);
144
y += i/2 - 1;
145
if (b_sz != 0 && c_sz != 0)
146
display.drawThickLine(display.width() / 2 - DASH_WIDTH / 2, y, display.width() / 2 + DASH_WIDTH / 2, y, 1, 3);
147
y += i/2 + 2;
148
displaySizedUp(c, y);
149
}
150
151
static void displayTextCenteredThird(char const *text, int third)
152
{
153
uint16_t centre_h = display.height() / 3;
154
centre_h = centre_h * third + centre_h / 2;
155
156
int16_t _x, _y;
157
uint16_t w, h;
158
display.getTextBounds(text, 0, 0, &_x, &_y, &w, &h);
159
160
display.setCursor((display.width() - w) / 2, centre_h - h / 2);
161
display.print(text);
162
}
163
164
// static void displayTextRightLn(char const* text)
165
// {
166
// int16_t x, y;
167
// uint16_t w, h;
168
// display.getTextBounds(text, 0, 0, &x, &y, &w, &h);
169
// display.setCursor(display.width() - w, display.getCursorY());
170
// display.println(text);
171
// }
172
173
const char note[] =
174
".........-####---"
175
".........-######-"
176
".........-##--###"
177
".........-##-----"
178
".........-##-...."
179
"..--------##-...."
180
"---######-##-...."
181
"-###########-...."
182
"-###########-...."
183
"---######----...."
184
"..--------.......";
185
186
const char heart[] =
187
"....####...####.."
188
"...######.######."
189
"..###############"
190
"..###############"
191
"...#############."
192
"....###########.."
193
".....#########..."
194
".......#####....."
195
"........###......"
196
".........#......."
197
".................";
198
199
const uint16_t note_w = 17;
200
const uint16_t note_h = 11;
201
const uint16_t note_y = 5;
202
203
const uint16_t note_wscale = 3;
204
const uint16_t note_hscale = 6;
205
206
static void drawGraphic(char const *gfx)
207
{
208
uint16_t i = 0;
209
const uint16_t note_x = display.width() - 5 - note_w * note_wscale;
210
for (uint16_t y = note_y; y < note_y + note_h * note_hscale; y += note_hscale)
211
for (uint16_t x = note_x; x < note_x + note_w * note_wscale; x += note_wscale, ++i)
212
if (gfx[i] == '#') {
213
for (uint16_t sy = y; sy < y + note_hscale; ++sy)
214
for (uint16_t sx = x; sx < x + note_wscale; ++sx)
215
display.drawPixel(sx, sy, 1);
216
} else if (gfx[i] == '-') {
217
for (uint16_t sy = y; sy < y + note_hscale; ++sy)
218
for (uint16_t sx = x; sx < x + note_wscale; ++sx)
219
display.drawPixel(sx, sy, 0);
220
}
221
}
222
223
static void refresh()
224
{
225
display.clearDisplay();
226
display.setTextSize(7);
227
if (request.kind == RequestKind::SUENA) {
228
displayTextCenteredThirds(
229
request.title,
230
request.artist,
231
request.album);
232
drawGraphic(note);
233
} else if (request.kind == RequestKind::NO_SUENA) {
234
displayTextCenteredThirds("", request.msg, "");
235
drawGraphic(heart);
236
} else {
237
displayTextCenteredThird("no esta sonando", 1);
238
}
239
display.display();
240
}
241
242
static void connectWifi()
243
{
244
display.clearDisplay();
245
displayTextCenteredThird(CONNECTING, 1);
246
display.display();
247
248
WiFi.mode(WIFI_STA);
249
WiFi.setHostname(HOSTNAME);
250
bool attempted = false;
251
252
while (true) {
253
int status = WiFi.status();
254
if (status == WL_CONNECTED) {
255
break;
256
} else if (!attempted) {
257
WiFi.begin(WIFI_SSID, WIFI_PASS);
258
attempted = true;
259
} else if (status == WL_CONNECT_FAILED) {
260
display.clearDisplay();
261
displayTextCenteredThird(CONNECT_FAILED, 1);
262
display.display();
263
delay(1000);
264
WiFi.begin(WIFI_SSID, WIFI_PASS);
265
}
266
delay(500);
267
}
268
269
server.begin();
270
}
271
272
void setup()
273
{
274
display.begin();
275
display.setTextColor(1, 0);
276
display.setTextWrap(false);
277
display.setTextSize(7);
278
279
connectWifi();
280
281
refresh();
282
}
283
284
#define URI_SUENA "/suena"
285
#define URI_NO_SUENA "/no_suena"
286
287
#define X_TITLE "x-title: "
288
#define X_ARTIST "x-artist: "
289
#define X_ALBUM "x-album: "
290
#define X_MSG "x-msg: "
291
292
static bool parseClientRequest(WiFiClient& client)
293
{
294
enum {
295
VERB, // "POST"
296
URI, // "/SECRET/suena"
297
HTTP, // "HTTP/1.1"
298
HEADERS, // etc.
299
} parse_state = VERB;
300
char buf[255];
301
uint8_t len = 0;
302
uint8_t parcels = 0;
303
request.kind = RequestKind::UNKNOWN;
304
305
unsigned long start = millis();
306
307
while (client.connected()) {
308
if (!client.available()) {
309
if (millis() - start > 5000) {
310
return false;
311
}
312
delay(10);
313
continue;
314
}
315
char c = client.read();
316
switch (parse_state) {
317
case VERB:
318
if (c >= 'A' && c <= 'Z' && len < 254) {
319
buf[len++] = c;
320
} else if (c == ' ') {
321
buf[len] = 0;
322
if (strcmp(buf, "POST") != 0) {
323
return false;
324
}
325
parse_state = URI;
326
len = 0;
327
} else {
328
return false;
329
}
330
break;
331
case URI:
332
if ((c == '/'
333
|| c == '_'
334
|| (c >= 'a' && c <= 'z')
335
|| (c >= '0' && c <= '9'))
336
&& len < 254) {
337
buf[len++] = c;
338
} else if (c == ' ') {
339
buf[len] = 0;
340
if (len < (sizeof(SECRET) - 1) + 1) {
341
return false;
342
}
343
if (strncmp(buf, "/" SECRET, sizeof(SECRET) - 1 + 1) != 0) {
344
return false;
345
}
346
if (strcmp(&buf[sizeof(SECRET) - 1 + 1], URI_SUENA) == 0) {
347
request.kind = RequestKind::SUENA;
348
} else if (strcmp(&buf[sizeof(SECRET) - 1 + 1], URI_NO_SUENA) == 0) {
349
request.kind = RequestKind::NO_SUENA;
350
} else {
351
return false;
352
}
353
parse_state = HTTP;
354
len = 0;
355
} else {
356
return false;
357
}
358
break;
359
case HTTP:
360
if ((c == '/'
361
|| c == '1'
362
|| c == '.'
363
|| c == '0'
364
|| (c >= 'A' && c <= 'Z'))
365
&& len < 254) {
366
buf[len++] = c;
367
} else if (c == '\r') {
368
buf[len] = 0;
369
if (strcmp(buf, "HTTP/1.0") == 0
370
|| strcmp(buf, "HTTP/1.1") == 0) {
371
// ok cool
372
} else {
373
// ok not cool
374
return false;
375
}
376
parse_state = HEADERS;
377
len = 1; // HEADERS loop not to think we're done when it sees \n
378
} else {
379
return false;
380
}
381
break;
382
case HEADERS:
383
if (c == '\n' && len == 0) {
384
// empty line, request fish
385
return (request.kind == RequestKind::SUENA && parcels == 3) ||
386
(request.kind == RequestKind::NO_SUENA && parcels == 1);
387
} else if (c == '\n') {
388
len = 0;
389
} else if (c == '\r') {
390
// process line if any
391
if (len > sizeof(X_TITLE) - 1 && strncmp(buf, X_TITLE, sizeof(X_TITLE) - 1) == 0) {
392
request.title = std::string(buf + sizeof(X_TITLE) - 1, len - sizeof(X_TITLE) + 1);
393
++parcels;
394
} else if (len > sizeof(X_ARTIST) - 1 && strncmp(buf, X_ARTIST, sizeof(X_ARTIST) - 1) == 0) {
395
request.artist = std::string(buf + sizeof(X_ARTIST) - 1, len - sizeof(X_ARTIST) + 1);
396
++parcels;
397
} else if (len > sizeof(X_ALBUM) - 1 && strncmp(buf, X_ALBUM, sizeof(X_ALBUM) - 1) == 0) {
398
request.album = std::string(buf + sizeof(X_ALBUM) - 1, len - sizeof(X_ALBUM) + 1);
399
++parcels;
400
} else if (len > sizeof(X_MSG) - 1 && strncmp(buf, X_MSG, sizeof(X_MSG) - 1) == 0) {
401
request.msg = std::string(buf + sizeof(X_MSG) - 1, len - sizeof(X_MSG) + 1);
402
++parcels;
403
}
404
} else {
405
buf[len] = c;
406
len++;
407
}
408
}
409
}
410
411
return false;
412
}
413
414
void loop()
415
{
416
if (WiFi.status() != WL_CONNECTED) {
417
connectWifi();
418
refresh();
419
}
420
421
WiFiClient client = server.available();
422
if (client) {
423
bool success = false;
424
if (parseClientRequest(client)) {
425
switch (request.kind) {
426
case RequestKind::UNKNOWN:
427
break;
428
case RequestKind::SUENA:
429
success = true;
430
break;
431
case RequestKind::NO_SUENA:
432
success = true;
433
break;
434
}
435
}
436
if (success) {
437
client.println("HTTP/1.1 200 OK");
438
client.println("Content-Type: text/plain");
439
client.println("Connection: close");
440
client.println();
441
client.println("que bella");
442
refresh();
443
} else {
444
client.println("HTTP/1.1 400 Bad Request");
445
client.println("Content-Type: text/plain");
446
client.println("Connection: close");
447
client.println();
448
client.println("yo bro wtf is this");
449
}
450
client.stop();
451
}
452
delay(5);
453
}
454