discstation 0.1.29 → 0.1.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -8
- package/arduino/{v1 → DiscStation}/DiscStation.ino +200 -148
- package/package.json +1 -1
- package/src/discstation.py +1 -1
- package/src/discstation_burn.py +15 -2
- package/arduino/c6/DiscStation_C6.ino +0 -1185
- package/arduino/c6/secrets.h.example +0 -14
- /package/arduino/{v1 → DiscStation}/secrets.h.example +0 -0
|
@@ -1,1185 +0,0 @@
|
|
|
1
|
-
#include <Wire.h>
|
|
2
|
-
#include <Adafruit_GFX.h>
|
|
3
|
-
#include <Adafruit_SSD1306.h>
|
|
4
|
-
#include <WiFi.h>
|
|
5
|
-
#include "esp_mac.h"
|
|
6
|
-
#include <WiFiManager.h>
|
|
7
|
-
#include <ESPmDNS.h>
|
|
8
|
-
#include <Preferences.h>
|
|
9
|
-
#include <ArduinoOTA.h>
|
|
10
|
-
#include <QRCode.h>
|
|
11
|
-
|
|
12
|
-
// Optional build-time Wi-Fi override for power users: copy secrets.h.example
|
|
13
|
-
// to secrets.h (git-ignored) and set WIFI_SSID / WIFI_PASS / OTA_PASSWORD.
|
|
14
|
-
#if __has_include("secrets.h")
|
|
15
|
-
#include "secrets.h"
|
|
16
|
-
#endif
|
|
17
|
-
|
|
18
|
-
#define SCREEN_WIDTH 128
|
|
19
|
-
#define SCREEN_HEIGHT 64
|
|
20
|
-
#define OLED_RESET -1
|
|
21
|
-
#define I2C_ADDRESS 0x3C
|
|
22
|
-
#define TCP_PORT 2323
|
|
23
|
-
|
|
24
|
-
#define BTN_SELECT_PIN 21 // D3
|
|
25
|
-
#define BTN_UP_PIN 20 // D9/MISO
|
|
26
|
-
#define BTN_DOWN_PIN 18 // D10/MOSI
|
|
27
|
-
#define POT_PIN 0 // D0/A0 (ADC)
|
|
28
|
-
|
|
29
|
-
#define DEBOUNCE_MS 50
|
|
30
|
-
#define LONG_PRESS_MS 1000
|
|
31
|
-
#define POT_READ_MS 200
|
|
32
|
-
#define DONE_RESET_MS 30000
|
|
33
|
-
#define STANDBY_BLANK_MS 60000
|
|
34
|
-
#define IDLE_BLANK_MS 45000 // no input this long on HOME/STANDBY -> spinning-disc screensaver
|
|
35
|
-
#define SAVER_FRAME_MS 90 // screensaver frame interval (~11fps)
|
|
36
|
-
#define PING_TIMEOUT_MS 30000
|
|
37
|
-
|
|
38
|
-
#define WIFI_RESET_HOLD_MS 10000
|
|
39
|
-
#define WIFI_CONNECT_TIMEOUT_MS 18000
|
|
40
|
-
#define WIFI_RETRY_MS 15000
|
|
41
|
-
|
|
42
|
-
#define MAX_HOME_MODES 5
|
|
43
|
-
#define BURN_COUNT 4
|
|
44
|
-
#define SPEED_COUNT 4
|
|
45
|
-
|
|
46
|
-
int homeCount = 3;
|
|
47
|
-
String homeModes[MAX_HOME_MODES] = {"BURN", "PLAY", "RIP"};
|
|
48
|
-
String discName = "";
|
|
49
|
-
|
|
50
|
-
// --- Wi-Fi link: mirror every outbound line to Serial AND a TCP client ---
|
|
51
|
-
WiFiServer tcpServer(TCP_PORT);
|
|
52
|
-
WiFiClient tcpClient;
|
|
53
|
-
WiFiManager wm;
|
|
54
|
-
Preferences prefs;
|
|
55
|
-
bool wifiConnected = false;
|
|
56
|
-
bool wifiInitDone = false;
|
|
57
|
-
bool portalActive = false;
|
|
58
|
-
bool otaEnabled = false;
|
|
59
|
-
bool credsJustSaved = false;
|
|
60
|
-
bool wifiResetArmed = false;
|
|
61
|
-
unsigned long wifiDropAt = 0;
|
|
62
|
-
String apName = "DiscStation";
|
|
63
|
-
String mdnsHost = "discstation";
|
|
64
|
-
|
|
65
|
-
class DualPrint : public Print {
|
|
66
|
-
public:
|
|
67
|
-
void setClient(WiFiClient* c) { _client = c; }
|
|
68
|
-
size_t write(uint8_t c) override {
|
|
69
|
-
Serial.write(c);
|
|
70
|
-
if (_client && _client->connected()) _client->write(c);
|
|
71
|
-
return 1;
|
|
72
|
-
}
|
|
73
|
-
size_t write(const uint8_t* buf, size_t len) override {
|
|
74
|
-
Serial.write(buf, len);
|
|
75
|
-
if (_client && _client->connected()) _client->write(buf, len);
|
|
76
|
-
return len;
|
|
77
|
-
}
|
|
78
|
-
private:
|
|
79
|
-
WiFiClient* _client = nullptr;
|
|
80
|
-
} Out;
|
|
81
|
-
|
|
82
|
-
void drawSetup();
|
|
83
|
-
void drawWifiReset();
|
|
84
|
-
|
|
85
|
-
String deviceSuffix() {
|
|
86
|
-
uint8_t mac[6];
|
|
87
|
-
esp_read_mac(mac, ESP_MAC_WIFI_STA); // factory MAC from eFuse - valid before the Wi-Fi driver starts
|
|
88
|
-
char buf[5];
|
|
89
|
-
snprintf(buf, sizeof(buf), "%02X%02X", mac[4], mac[5]);
|
|
90
|
-
return String(buf);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
void loadCreds(String& ssid, String& pass) {
|
|
94
|
-
prefs.begin("discstation", true);
|
|
95
|
-
ssid = prefs.getString("ssid", "");
|
|
96
|
-
pass = prefs.getString("pass", "");
|
|
97
|
-
prefs.end();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
void saveCreds(const String& ssid, const String& pass) {
|
|
101
|
-
prefs.begin("discstation", false);
|
|
102
|
-
prefs.putString("ssid", ssid);
|
|
103
|
-
prefs.putString("pass", pass);
|
|
104
|
-
prefs.end();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
void clearCreds() {
|
|
108
|
-
prefs.begin("discstation", false);
|
|
109
|
-
prefs.clear();
|
|
110
|
-
prefs.end();
|
|
111
|
-
WiFi.disconnect(true, true);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
void wmSaveCallback() {
|
|
115
|
-
saveCreds(wm.getWiFiSSID(), wm.getWiFiPass());
|
|
116
|
-
credsJustSaved = true;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
void onWifiUp() {
|
|
120
|
-
portalActive = false;
|
|
121
|
-
wifiConnected = true;
|
|
122
|
-
wifiDropAt = 0;
|
|
123
|
-
WiFi.setSleep(WIFI_PS_MIN_MODEM);
|
|
124
|
-
tcpServer.begin();
|
|
125
|
-
if (MDNS.begin(mdnsHost.c_str())) {
|
|
126
|
-
MDNS.addService("discstation", "tcp", TCP_PORT);
|
|
127
|
-
}
|
|
128
|
-
#ifdef OTA_PASSWORD
|
|
129
|
-
ArduinoOTA.setHostname(mdnsHost.c_str());
|
|
130
|
-
ArduinoOTA.setPassword(OTA_PASSWORD);
|
|
131
|
-
ArduinoOTA.begin();
|
|
132
|
-
otaEnabled = true;
|
|
133
|
-
#endif
|
|
134
|
-
Out.print("WiFi OK "); Out.println(WiFi.localIP());
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
void startPortal() {
|
|
138
|
-
Out.print("WiFi: setup portal '"); Out.print(apName); Out.println("'");
|
|
139
|
-
portalActive = true;
|
|
140
|
-
WiFi.mode(WIFI_AP_STA);
|
|
141
|
-
wm.setConfigPortalBlocking(false);
|
|
142
|
-
wm.setConfigPortalTimeout(0);
|
|
143
|
-
wm.setSaveConfigCallback(wmSaveCallback);
|
|
144
|
-
wm.startConfigPortal(apName.c_str());
|
|
145
|
-
drawSetup();
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
void initWiFi() {
|
|
149
|
-
apName = "DiscStation-" + deviceSuffix();
|
|
150
|
-
mdnsHost = "discstation-" + deviceSuffix();
|
|
151
|
-
mdnsHost.toLowerCase();
|
|
152
|
-
|
|
153
|
-
WiFi.mode(WIFI_STA);
|
|
154
|
-
WiFi.setAutoReconnect(true);
|
|
155
|
-
WiFi.setSleep(WIFI_PS_MIN_MODEM);
|
|
156
|
-
|
|
157
|
-
String ssid, pass;
|
|
158
|
-
#ifdef WIFI_SSID
|
|
159
|
-
ssid = WIFI_SSID;
|
|
160
|
-
pass = WIFI_PASS;
|
|
161
|
-
#else
|
|
162
|
-
loadCreds(ssid, pass);
|
|
163
|
-
#endif
|
|
164
|
-
|
|
165
|
-
if (ssid.length() > 0) {
|
|
166
|
-
Out.print("WiFi "); Out.print(ssid); Out.print("...");
|
|
167
|
-
WiFi.begin(ssid.c_str(), pass.c_str());
|
|
168
|
-
unsigned long t0 = millis();
|
|
169
|
-
while (WiFi.status() != WL_CONNECTED && millis() - t0 < WIFI_CONNECT_TIMEOUT_MS) delay(200);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (WiFi.status() == WL_CONNECTED) {
|
|
173
|
-
Out.println(" OK");
|
|
174
|
-
onWifiUp();
|
|
175
|
-
} else {
|
|
176
|
-
if (ssid.length() > 0) Out.println(" fail (2.4GHz band / wrong password?)");
|
|
177
|
-
startPortal();
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
|
|
182
|
-
|
|
183
|
-
String ipUrl = "";
|
|
184
|
-
|
|
185
|
-
enum UiState {
|
|
186
|
-
UI_STATUS,
|
|
187
|
-
UI_HOME,
|
|
188
|
-
UI_BURN_READY,
|
|
189
|
-
UI_PLAY,
|
|
190
|
-
UI_STANDBY,
|
|
191
|
-
UI_WAITING,
|
|
192
|
-
UI_IP,
|
|
193
|
-
UI_DISCONNECTED,
|
|
194
|
-
UI_LOADING,
|
|
195
|
-
UI_SETUP
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
const char* BURN_MODES[BURN_COUNT] = {"AUTO", "BEST", "LONG", "TEST"};
|
|
199
|
-
const char* SPEED_MODES[SPEED_COUNT] = {"Auto", "6x", "4x", "3x"};
|
|
200
|
-
|
|
201
|
-
UiState uiState = UI_STATUS;
|
|
202
|
-
|
|
203
|
-
String titleLine = "";
|
|
204
|
-
String metaLine = "";
|
|
205
|
-
String fitLine = "";
|
|
206
|
-
String discLine = "Disc: none";
|
|
207
|
-
String line1 = "Status: READY";
|
|
208
|
-
String line2 = "";
|
|
209
|
-
String line3 = "";
|
|
210
|
-
String waitingLine1 = "";
|
|
211
|
-
String waitingLine2 = "";
|
|
212
|
-
String loadingLine = "";
|
|
213
|
-
int loadingDots = 0;
|
|
214
|
-
unsigned long lastLoadingAnim = 0;
|
|
215
|
-
int progressPercent = -1;
|
|
216
|
-
|
|
217
|
-
bool displayOk = false;
|
|
218
|
-
unsigned long returnToHomeAt = 0;
|
|
219
|
-
unsigned long playStatusAt = 0;
|
|
220
|
-
bool playStatusTemp = false;
|
|
221
|
-
unsigned long lastPotRead = 0;
|
|
222
|
-
|
|
223
|
-
unsigned long selectDownAt = 0;
|
|
224
|
-
bool selectDown = false;
|
|
225
|
-
unsigned long selectLastDebounce = 0;
|
|
226
|
-
|
|
227
|
-
unsigned long upDownAt = 0;
|
|
228
|
-
bool upDown = false;
|
|
229
|
-
unsigned long upLastDebounce = 0;
|
|
230
|
-
|
|
231
|
-
unsigned long downDownAt = 0;
|
|
232
|
-
bool downDown = false;
|
|
233
|
-
unsigned long downLastDebounce = 0;
|
|
234
|
-
|
|
235
|
-
int ffSpeedIndex = 0;
|
|
236
|
-
int rewSpeedIndex = 0;
|
|
237
|
-
|
|
238
|
-
int homeIndex = 0;
|
|
239
|
-
int burnModeIndex = 0;
|
|
240
|
-
int burnSpeedIndex = 0;
|
|
241
|
-
bool editingSpeed = false;
|
|
242
|
-
int playVolume = 50;
|
|
243
|
-
unsigned long standbyStartTime = 0;
|
|
244
|
-
unsigned long lastMsgTime = 0;
|
|
245
|
-
unsigned long lastInputTime = 0; // last button press / screen change; drives the idle screensaver
|
|
246
|
-
bool displayBlank = false; // true = real UI hidden, disc screensaver running; any input restores it
|
|
247
|
-
unsigned long lastSaverFrame = 0;
|
|
248
|
-
int saverStep = 0;
|
|
249
|
-
bool audioPlayMode = false;
|
|
250
|
-
int displayRotation = 0;
|
|
251
|
-
|
|
252
|
-
int indeterminateCount = 0;
|
|
253
|
-
unsigned long lastIndeterminateAnim = 0;
|
|
254
|
-
|
|
255
|
-
int readBucket(int count) {
|
|
256
|
-
int raw = analogRead(POT_PIN);
|
|
257
|
-
int idx = raw * count / 4096;
|
|
258
|
-
if (idx < 0) idx = 0;
|
|
259
|
-
if (idx >= count) idx = count - 1;
|
|
260
|
-
return idx;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
int readPercent() {
|
|
264
|
-
int raw = analogRead(POT_PIN);
|
|
265
|
-
int pct = raw * 100 / 4095;
|
|
266
|
-
if (pct < 0) pct = 0;
|
|
267
|
-
if (pct > 100) pct = 100;
|
|
268
|
-
return ((pct + 2) / 5) * 5;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
void sendHomeMode() {
|
|
272
|
-
if (homeIndex >= 0 && homeIndex < homeCount) {
|
|
273
|
-
Out.print("MENU:");
|
|
274
|
-
Out.println(homeModes[homeIndex]);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
void sendBurnMode() {
|
|
279
|
-
Out.print("MODE:");
|
|
280
|
-
Out.println(BURN_MODES[burnModeIndex]);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
void printUpper(String value) {
|
|
284
|
-
value.toUpperCase();
|
|
285
|
-
display.print(value);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
void drawChrome() {
|
|
289
|
-
display.setRotation(displayRotation);
|
|
290
|
-
display.setTextSize(1);
|
|
291
|
-
display.setTextColor(SSD1306_WHITE);
|
|
292
|
-
display.drawLine(0, 0, 4, 0, SSD1306_WHITE);
|
|
293
|
-
display.drawLine(0, 0, 0, 4, SSD1306_WHITE);
|
|
294
|
-
display.drawLine(123, 0, 127, 0, SSD1306_WHITE);
|
|
295
|
-
display.drawLine(127, 0, 127, 4, SSD1306_WHITE);
|
|
296
|
-
display.drawLine(0, 63, 4, 63, SSD1306_WHITE);
|
|
297
|
-
display.drawLine(0, 59, 0, 63, SSD1306_WHITE);
|
|
298
|
-
display.drawLine(123, 63, 127, 63, SSD1306_WHITE);
|
|
299
|
-
display.drawLine(127, 59, 127, 63, SSD1306_WHITE);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
void drawHeader() {
|
|
303
|
-
display.setTextSize(1);
|
|
304
|
-
display.setTextColor(SSD1306_WHITE);
|
|
305
|
-
drawChrome();
|
|
306
|
-
display.setCursor(4, 0);
|
|
307
|
-
display.print("DISCSTATION // PANEL");
|
|
308
|
-
display.drawLine(6, 10, 121, 10, SSD1306_WHITE);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
void drawHome() {
|
|
312
|
-
uiState = UI_HOME;
|
|
313
|
-
returnToHomeAt = 0;
|
|
314
|
-
lastInputTime = millis();
|
|
315
|
-
if (!displayOk) return;
|
|
316
|
-
|
|
317
|
-
display.clearDisplay();
|
|
318
|
-
drawHeader();
|
|
319
|
-
|
|
320
|
-
int y = 14;
|
|
321
|
-
if (discName.length() > 0) {
|
|
322
|
-
display.setTextSize(1);
|
|
323
|
-
display.setCursor(2, y);
|
|
324
|
-
printUpper(discName);
|
|
325
|
-
y += 10;
|
|
326
|
-
} else {
|
|
327
|
-
display.setCursor(2, y);
|
|
328
|
-
printUpper(discLine);
|
|
329
|
-
y += 10;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
for (int i = 0; i < homeCount; i++) {
|
|
333
|
-
display.setCursor(2, y);
|
|
334
|
-
display.print(i == homeIndex ? ">" : " ");
|
|
335
|
-
display.setCursor(14, y);
|
|
336
|
-
printUpper(homeModes[i]);
|
|
337
|
-
y += 10;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
display.display();
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
void drawBurnReady() {
|
|
344
|
-
uiState = UI_BURN_READY;
|
|
345
|
-
returnToHomeAt = 0;
|
|
346
|
-
if (!displayOk) return;
|
|
347
|
-
|
|
348
|
-
display.clearDisplay();
|
|
349
|
-
drawHeader();
|
|
350
|
-
|
|
351
|
-
display.setCursor(2, 14);
|
|
352
|
-
printUpper(titleLine);
|
|
353
|
-
|
|
354
|
-
display.setCursor(2, 25);
|
|
355
|
-
printUpper(metaLine);
|
|
356
|
-
|
|
357
|
-
display.setCursor(2, 37);
|
|
358
|
-
printUpper(fitLine);
|
|
359
|
-
|
|
360
|
-
display.setCursor(2, 49);
|
|
361
|
-
display.print(editingSpeed ? ' ' : '>');
|
|
362
|
-
display.setCursor(14, 49);
|
|
363
|
-
display.print(BURN_MODES[burnModeIndex]);
|
|
364
|
-
|
|
365
|
-
display.setCursor(62, 49);
|
|
366
|
-
display.print(editingSpeed ? '>' : ' ');
|
|
367
|
-
display.setCursor(74, 49);
|
|
368
|
-
display.print(SPEED_MODES[burnSpeedIndex]);
|
|
369
|
-
|
|
370
|
-
display.setCursor(2, 57);
|
|
371
|
-
display.print("HOLD SELECT // GO");
|
|
372
|
-
|
|
373
|
-
display.display();
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
void drawWaiting() {
|
|
377
|
-
uiState = UI_WAITING;
|
|
378
|
-
returnToHomeAt = 0;
|
|
379
|
-
if (!displayOk) return;
|
|
380
|
-
|
|
381
|
-
display.clearDisplay();
|
|
382
|
-
drawHeader();
|
|
383
|
-
|
|
384
|
-
display.setCursor(2, 16);
|
|
385
|
-
printUpper(waitingLine1);
|
|
386
|
-
|
|
387
|
-
display.setCursor(2, 30);
|
|
388
|
-
printUpper(waitingLine2);
|
|
389
|
-
|
|
390
|
-
display.setCursor(2, 49);
|
|
391
|
-
display.print("SHORT // GO");
|
|
392
|
-
|
|
393
|
-
display.display();
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
void drawStatus() {
|
|
397
|
-
uiState = UI_STATUS;
|
|
398
|
-
if (!displayOk) return;
|
|
399
|
-
|
|
400
|
-
display.clearDisplay();
|
|
401
|
-
drawHeader();
|
|
402
|
-
|
|
403
|
-
display.setCursor(2, 16);
|
|
404
|
-
printUpper(line1);
|
|
405
|
-
display.setCursor(2, 30);
|
|
406
|
-
printUpper(line2);
|
|
407
|
-
display.setCursor(2, 44);
|
|
408
|
-
printUpper(line3);
|
|
409
|
-
|
|
410
|
-
if (progressPercent >= 0) {
|
|
411
|
-
int barW = map(progressPercent, 0, 100, 0, 124);
|
|
412
|
-
display.drawRect(2, 54, 124, 8, SSD1306_WHITE);
|
|
413
|
-
if (barW > 0) {
|
|
414
|
-
display.fillRect(2, 54, barW, 8, SSD1306_WHITE);
|
|
415
|
-
}
|
|
416
|
-
} else {
|
|
417
|
-
String dots = "";
|
|
418
|
-
for (int i = 0; i < indeterminateCount; i++) dots += ".";
|
|
419
|
-
display.setCursor(2, 56);
|
|
420
|
-
display.print(dots);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
display.display();
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
void drawQrCode(String value) {
|
|
427
|
-
drawChrome();
|
|
428
|
-
QRCode qr;
|
|
429
|
-
uint8_t qrData[qrcode_getBufferSize(2)];
|
|
430
|
-
if (qrcode_initText(&qr, qrData, 2, ECC_LOW, value.c_str()) < 0) {
|
|
431
|
-
display.setCursor(2, 25);
|
|
432
|
-
display.print("QR ERROR // USE URL");
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
|
-
const int scale = 2;
|
|
436
|
-
const int quiet = 2;
|
|
437
|
-
const int pixels = qr.size * scale;
|
|
438
|
-
const int left = (SCREEN_WIDTH - pixels) / 2;
|
|
439
|
-
const int top = 2;
|
|
440
|
-
display.fillRect(left - quiet, top - quiet, pixels + quiet * 2, pixels + quiet * 2, SSD1306_WHITE);
|
|
441
|
-
for (uint8_t y = 0; y < qr.size; y++) {
|
|
442
|
-
for (uint8_t x = 0; x < qr.size; x++) {
|
|
443
|
-
if (qrcode_getModule(&qr, x, y)) {
|
|
444
|
-
display.fillRect(left + x * scale, top + y * scale, scale, scale, SSD1306_BLACK);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
display.setCursor(2, 57);
|
|
449
|
-
display.print("SCAN // DISCSTATION");
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
void drawIP() {
|
|
453
|
-
uiState = UI_IP;
|
|
454
|
-
returnToHomeAt = 0;
|
|
455
|
-
if (!displayOk) return;
|
|
456
|
-
|
|
457
|
-
display.clearDisplay();
|
|
458
|
-
drawQrCode(ipUrl);
|
|
459
|
-
|
|
460
|
-
display.display();
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
void drawStandby() {
|
|
464
|
-
if (portalActive) { drawSetup(); return; }
|
|
465
|
-
uiState = UI_STANDBY;
|
|
466
|
-
returnToHomeAt = 0;
|
|
467
|
-
standbyStartTime = millis();
|
|
468
|
-
lastInputTime = millis();
|
|
469
|
-
displayBlank = false;
|
|
470
|
-
if (!displayOk) return;
|
|
471
|
-
|
|
472
|
-
display.clearDisplay();
|
|
473
|
-
drawHeader();
|
|
474
|
-
display.setCursor(2, 25);
|
|
475
|
-
display.print("STANDBY // READY");
|
|
476
|
-
display.setCursor(2, 40);
|
|
477
|
-
display.print("INSERT MEDIA");
|
|
478
|
-
display.display();
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
void drawSetup() {
|
|
482
|
-
uiState = UI_SETUP;
|
|
483
|
-
returnToHomeAt = 0;
|
|
484
|
-
displayBlank = false;
|
|
485
|
-
lastInputTime = millis();
|
|
486
|
-
if (!displayOk) return;
|
|
487
|
-
display.clearDisplay();
|
|
488
|
-
drawHeader();
|
|
489
|
-
display.setCursor(2, 16);
|
|
490
|
-
display.print("WIFI SETUP");
|
|
491
|
-
display.setCursor(2, 28);
|
|
492
|
-
display.print("JOIN:");
|
|
493
|
-
display.setCursor(2, 38);
|
|
494
|
-
display.print(apName);
|
|
495
|
-
display.setCursor(2, 50);
|
|
496
|
-
display.print("THEN 192.168.4.1");
|
|
497
|
-
display.display();
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
void drawWifiReset() {
|
|
501
|
-
uiState = UI_SETUP;
|
|
502
|
-
displayBlank = false;
|
|
503
|
-
if (!displayOk) return;
|
|
504
|
-
display.clearDisplay();
|
|
505
|
-
drawHeader();
|
|
506
|
-
display.setCursor(2, 25);
|
|
507
|
-
display.print("WIFI RESET");
|
|
508
|
-
display.setCursor(2, 40);
|
|
509
|
-
display.print("REBOOTING...");
|
|
510
|
-
display.display();
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
void drawDisconnected() {
|
|
514
|
-
uiState = UI_DISCONNECTED;
|
|
515
|
-
returnToHomeAt = 0;
|
|
516
|
-
displayBlank = false;
|
|
517
|
-
if (!displayOk) return;
|
|
518
|
-
|
|
519
|
-
display.clearDisplay();
|
|
520
|
-
drawHeader();
|
|
521
|
-
display.setCursor(2, 25);
|
|
522
|
-
display.print("DISCONNECTED // LINK");
|
|
523
|
-
display.setCursor(2, 40);
|
|
524
|
-
display.print("CHECK USB");
|
|
525
|
-
display.display();
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
void drawLoading() {
|
|
529
|
-
uiState = UI_LOADING;
|
|
530
|
-
returnToHomeAt = 0;
|
|
531
|
-
displayBlank = false;
|
|
532
|
-
if (!displayOk) return;
|
|
533
|
-
|
|
534
|
-
display.clearDisplay();
|
|
535
|
-
drawHeader();
|
|
536
|
-
|
|
537
|
-
display.setTextSize(1);
|
|
538
|
-
display.setCursor(2, 18);
|
|
539
|
-
printUpper(loadingLine);
|
|
540
|
-
|
|
541
|
-
String dots = "";
|
|
542
|
-
for (int i = 0; i < loadingDots; i++) dots += ".";
|
|
543
|
-
display.setCursor(2, 32);
|
|
544
|
-
display.print(dots);
|
|
545
|
-
display.setCursor(70, 32);
|
|
546
|
-
display.print("LOADING //");
|
|
547
|
-
|
|
548
|
-
display.display();
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
// 24-step sine table x64: sin(i*15deg)*64. Drives the idle disc screensaver.
|
|
552
|
-
const int8_t SIN24[24] = {
|
|
553
|
-
0, 17, 32, 45, 55, 62, 64, 62, 55, 45, 32, 17,
|
|
554
|
-
0, -17, -32, -45, -55, -62, -64, -62, -55, -45, -32, -17
|
|
555
|
-
};
|
|
556
|
-
|
|
557
|
-
// Spinning-disc screensaver frame - shown after IDLE_BLANK_MS on HOME/STANDBY
|
|
558
|
-
// instead of powering the panel off. Keeps the OLED lit and the I2C bus busy so
|
|
559
|
-
// a USB power-bank feeding the remote doesn't hit its no-load auto-shutoff.
|
|
560
|
-
void drawDiscSaver(int step) {
|
|
561
|
-
if (!displayOk) return;
|
|
562
|
-
const int cx = 64, cy = 32, R = 30;
|
|
563
|
-
display.clearDisplay();
|
|
564
|
-
display.fillCircle(cx, cy, R, SSD1306_WHITE); // vinyl body
|
|
565
|
-
|
|
566
|
-
for (int cl = 0; cl < 2; cl++) { // 2 groove clusters, 180 apart
|
|
567
|
-
int base = step + cl * 12;
|
|
568
|
-
for (int g = 0; g < 3; g++) { // 3 nested "sound wave" grooves
|
|
569
|
-
for (int t = 0; t < 3; t++) { // 3px-thick stroke
|
|
570
|
-
int r = 16 + g * 5 + t;
|
|
571
|
-
int px = -100, py = -100;
|
|
572
|
-
for (int a = 0; a <= 4; a++) { // ~60deg arc, 4 chords
|
|
573
|
-
int i = (base + a) % 24;
|
|
574
|
-
int x = cx + r * SIN24[(i + 6) % 24] / 64;
|
|
575
|
-
int y = cy + r * SIN24[i] / 64;
|
|
576
|
-
if (px > -100) display.drawLine(px, py, x, y, SSD1306_BLACK);
|
|
577
|
-
px = x; py = y;
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
display.fillCircle(cx, cy, 13, SSD1306_BLACK); // concentric label
|
|
584
|
-
display.fillCircle(cx, cy, 11, SSD1306_WHITE);
|
|
585
|
-
display.fillCircle(cx, cy, 8, SSD1306_BLACK);
|
|
586
|
-
display.fillCircle(cx, cy, 5, SSD1306_WHITE);
|
|
587
|
-
display.fillCircle(cx, cy, 2, SSD1306_BLACK); // center hole
|
|
588
|
-
display.display();
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
// Returns true if this call actually woke a blanked screen - callers use that
|
|
592
|
-
// to swallow the wake press so it doesn't also trigger a menu action.
|
|
593
|
-
bool wakeDisplay() {
|
|
594
|
-
if (!displayBlank || !displayOk) return false;
|
|
595
|
-
display.ssd1306_command(0xAF);
|
|
596
|
-
displayBlank = false;
|
|
597
|
-
switch (uiState) {
|
|
598
|
-
case UI_HOME: drawHome(); break;
|
|
599
|
-
case UI_STATUS: drawStatus(); break;
|
|
600
|
-
case UI_IP: drawIP(); break;
|
|
601
|
-
case UI_BURN_READY: drawBurnReady(); break;
|
|
602
|
-
case UI_PLAY: drawPlay(); break;
|
|
603
|
-
case UI_WAITING: drawWaiting(); break;
|
|
604
|
-
case UI_STANDBY: drawStandby(); break;
|
|
605
|
-
case UI_DISCONNECTED: drawDisconnected(); break;
|
|
606
|
-
case UI_LOADING: drawLoading(); break;
|
|
607
|
-
case UI_SETUP: drawSetup(); break;
|
|
608
|
-
}
|
|
609
|
-
return true;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
void drawPlay() {
|
|
613
|
-
uiState = UI_PLAY;
|
|
614
|
-
returnToHomeAt = 0;
|
|
615
|
-
if (!displayOk) return;
|
|
616
|
-
|
|
617
|
-
display.clearDisplay();
|
|
618
|
-
drawHeader();
|
|
619
|
-
|
|
620
|
-
display.setCursor(2, 16);
|
|
621
|
-
printUpper(line1);
|
|
622
|
-
display.setCursor(2, 30);
|
|
623
|
-
printUpper(line2);
|
|
624
|
-
display.setCursor(2, 44);
|
|
625
|
-
display.print("VOL // ");
|
|
626
|
-
display.print(playVolume);
|
|
627
|
-
display.print("%");
|
|
628
|
-
if (!audioPlayMode) {
|
|
629
|
-
display.setCursor(2, 56);
|
|
630
|
-
display.print("SELECT // PLAY");
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
display.display();
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
void parseMessage(String msg) {
|
|
637
|
-
msg.trim();
|
|
638
|
-
|
|
639
|
-
if (msg == "PING") {
|
|
640
|
-
lastMsgTime = millis();
|
|
641
|
-
Out.println("PONG");
|
|
642
|
-
if (uiState == UI_DISCONNECTED) drawStandby();
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
wakeDisplay();
|
|
647
|
-
lastMsgTime = millis();
|
|
648
|
-
|
|
649
|
-
Out.print("RCV:");
|
|
650
|
-
Out.println(msg);
|
|
651
|
-
|
|
652
|
-
if (msg.startsWith("HOME:")) {
|
|
653
|
-
homeIndex = readBucket(homeCount);
|
|
654
|
-
drawHome();
|
|
655
|
-
|
|
656
|
-
} else if (msg.startsWith("DISC:")) {
|
|
657
|
-
discLine = msg.substring(5);
|
|
658
|
-
if (discLine.length() > 20) discLine = discLine.substring(0, 20);
|
|
659
|
-
if (uiState == UI_HOME) drawHome();
|
|
660
|
-
|
|
661
|
-
} else if (msg.startsWith("DISC_NAME:")) {
|
|
662
|
-
discName = msg.substring(10);
|
|
663
|
-
if (discName.length() > 18) discName = discName.substring(0, 18);
|
|
664
|
-
if (uiState == UI_HOME) drawHome();
|
|
665
|
-
|
|
666
|
-
} else if (msg.startsWith("MENU_ITEMS:")) {
|
|
667
|
-
String items = msg.substring(11);
|
|
668
|
-
homeCount = 0;
|
|
669
|
-
while (items.length() > 0 && homeCount < MAX_HOME_MODES) {
|
|
670
|
-
int comma = items.indexOf(',');
|
|
671
|
-
if (comma < 0) {
|
|
672
|
-
homeModes[homeCount++] = items;
|
|
673
|
-
break;
|
|
674
|
-
}
|
|
675
|
-
homeModes[homeCount++] = items.substring(0, comma);
|
|
676
|
-
items = items.substring(comma + 1);
|
|
677
|
-
}
|
|
678
|
-
if (homeIndex >= homeCount) homeIndex = 0;
|
|
679
|
-
if (uiState == UI_HOME) drawHome();
|
|
680
|
-
|
|
681
|
-
} else if (msg.startsWith("STANDBY:")) {
|
|
682
|
-
drawStandby();
|
|
683
|
-
|
|
684
|
-
} else if (msg.startsWith("TITLE:")) {
|
|
685
|
-
titleLine = msg.substring(6);
|
|
686
|
-
if (titleLine.length() > 20) titleLine = titleLine.substring(0, 20);
|
|
687
|
-
burnModeIndex = readBucket(BURN_COUNT);
|
|
688
|
-
drawBurnReady();
|
|
689
|
-
|
|
690
|
-
} else if (msg.startsWith("META:")) {
|
|
691
|
-
metaLine = msg.substring(5);
|
|
692
|
-
if (metaLine.length() > 20) metaLine = metaLine.substring(0, 20);
|
|
693
|
-
drawBurnReady();
|
|
694
|
-
|
|
695
|
-
} else if (msg.startsWith("FIT:")) {
|
|
696
|
-
fitLine = msg.substring(4);
|
|
697
|
-
if (fitLine.length() > 20) fitLine = fitLine.substring(0, 20);
|
|
698
|
-
drawBurnReady();
|
|
699
|
-
|
|
700
|
-
} else if (msg.startsWith("PLAY:")) {
|
|
701
|
-
line1 = msg.substring(5);
|
|
702
|
-
line2 = "Playing disc";
|
|
703
|
-
playVolume = readPercent();
|
|
704
|
-
Out.print("POT:"); // push the knob's current level so playback starts at it
|
|
705
|
-
Out.println(playVolume);
|
|
706
|
-
drawPlay();
|
|
707
|
-
|
|
708
|
-
} else if (msg.startsWith("PLAY_MODE:")) {
|
|
709
|
-
audioPlayMode = msg.substring(10) == "AUDIO_CD";
|
|
710
|
-
drawPlay();
|
|
711
|
-
|
|
712
|
-
} else if (msg.startsWith("PLAY_STATUS:")) {
|
|
713
|
-
line1 = msg.substring(12);
|
|
714
|
-
if (line1.length() > 20) line1 = line1.substring(0, 20);
|
|
715
|
-
if (line1 == "PLAYING" || line1 == "PAUSED" || (audioPlayMode && line1.startsWith("TRACK "))) {
|
|
716
|
-
playStatusTemp = false;
|
|
717
|
-
} else {
|
|
718
|
-
playStatusAt = millis();
|
|
719
|
-
playStatusTemp = true;
|
|
720
|
-
}
|
|
721
|
-
drawPlay();
|
|
722
|
-
|
|
723
|
-
} else if (msg.startsWith("WAITING:")) {
|
|
724
|
-
returnToHomeAt = 0;
|
|
725
|
-
String rest = msg.substring(8);
|
|
726
|
-
int slash = rest.indexOf('/');
|
|
727
|
-
if (slash >= 0) {
|
|
728
|
-
waitingLine1 = "Data: " + rest.substring(0, slash);
|
|
729
|
-
waitingLine1.trim();
|
|
730
|
-
waitingLine2 = "Disc: " + rest.substring(slash + 1);
|
|
731
|
-
waitingLine2.trim();
|
|
732
|
-
} else {
|
|
733
|
-
waitingLine1 = rest;
|
|
734
|
-
waitingLine2 = "";
|
|
735
|
-
}
|
|
736
|
-
drawWaiting();
|
|
737
|
-
|
|
738
|
-
} else if (msg.startsWith("STATUS:")) {
|
|
739
|
-
returnToHomeAt = 0;
|
|
740
|
-
line1 = msg.substring(7);
|
|
741
|
-
line2 = "";
|
|
742
|
-
line3 = "";
|
|
743
|
-
// Don't reset progressPercent here - a STATUS: label change (e.g.
|
|
744
|
-
// "Copying files..." -> "Converting...") is often immediately
|
|
745
|
-
// followed by a fresh PROGRESS: for the same ongoing operation.
|
|
746
|
-
// Wiping it every time flipped the bar to the indeterminate dots
|
|
747
|
-
// and back on every single label update, flickering between the
|
|
748
|
-
// two. DONE:/CANCELLED:/ERROR:/NO_DISC: still reset it below - those
|
|
749
|
-
// really are the end of an operation.
|
|
750
|
-
drawStatus();
|
|
751
|
-
|
|
752
|
-
} else if (msg.startsWith("PROGRESS:")) {
|
|
753
|
-
returnToHomeAt = 0;
|
|
754
|
-
line2 = msg.substring(9);
|
|
755
|
-
{
|
|
756
|
-
String rest = msg.substring(9);
|
|
757
|
-
int pct = rest.indexOf('%');
|
|
758
|
-
if (pct > 0) {
|
|
759
|
-
String num = rest.substring(0, pct);
|
|
760
|
-
num.trim();
|
|
761
|
-
int val = num.toInt();
|
|
762
|
-
if (val >= 0 && val <= 100) {
|
|
763
|
-
progressPercent = val;
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
drawStatus();
|
|
768
|
-
|
|
769
|
-
} else if (msg.startsWith("INFO:")) {
|
|
770
|
-
returnToHomeAt = 0;
|
|
771
|
-
line3 = msg.substring(5);
|
|
772
|
-
drawStatus();
|
|
773
|
-
|
|
774
|
-
} else if (msg.startsWith("DONE:")) {
|
|
775
|
-
line1 = "DONE!";
|
|
776
|
-
line2 = msg.substring(5);
|
|
777
|
-
line3 = "Returning home";
|
|
778
|
-
returnToHomeAt = millis() + DONE_RESET_MS;
|
|
779
|
-
progressPercent = -1;
|
|
780
|
-
drawStatus();
|
|
781
|
-
|
|
782
|
-
} else if (msg.startsWith("CANCELLED:")) {
|
|
783
|
-
returnToHomeAt = 0;
|
|
784
|
-
line1 = "CANCELLED";
|
|
785
|
-
line2 = msg.substring(10);
|
|
786
|
-
line3 = "Use menu";
|
|
787
|
-
progressPercent = -1;
|
|
788
|
-
drawStatus();
|
|
789
|
-
|
|
790
|
-
} else if (msg.startsWith("WARNING:")) {
|
|
791
|
-
returnToHomeAt = 0;
|
|
792
|
-
line1 = "!! WARNING !!";
|
|
793
|
-
line2 = msg.substring(8);
|
|
794
|
-
if (line2.length() > 20) line2 = line2.substring(0, 20);
|
|
795
|
-
line3 = "";
|
|
796
|
-
drawStatus();
|
|
797
|
-
|
|
798
|
-
} else if (msg.startsWith("ERROR:")) {
|
|
799
|
-
returnToHomeAt = 0;
|
|
800
|
-
line1 = "!! ERROR";
|
|
801
|
-
line2 = msg.substring(6);
|
|
802
|
-
line3 = "";
|
|
803
|
-
progressPercent = -1;
|
|
804
|
-
drawStatus();
|
|
805
|
-
|
|
806
|
-
} else if (msg.startsWith("IP:")) {
|
|
807
|
-
ipUrl = msg.substring(3);
|
|
808
|
-
drawIP();
|
|
809
|
-
|
|
810
|
-
} else if (msg.startsWith("LOADING:")) {
|
|
811
|
-
loadingLine = msg.substring(8);
|
|
812
|
-
loadingDots = 0;
|
|
813
|
-
lastLoadingAnim = millis();
|
|
814
|
-
drawLoading();
|
|
815
|
-
|
|
816
|
-
} else if (msg.startsWith("NO_DISC:")) {
|
|
817
|
-
line1 = msg.substring(8);
|
|
818
|
-
line2 = "";
|
|
819
|
-
line3 = "";
|
|
820
|
-
progressPercent = -1;
|
|
821
|
-
drawStatus();
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
void setup() {
|
|
826
|
-
setCpuFrequencyMhz(160); // 240 -> 160: ~halves CPU power; I2C/GPIO all fine at 160
|
|
827
|
-
Serial.begin(115200);
|
|
828
|
-
Wire.begin(22, 23);
|
|
829
|
-
|
|
830
|
-
pinMode(3, OUTPUT);
|
|
831
|
-
digitalWrite(3, LOW);
|
|
832
|
-
pinMode(14, OUTPUT);
|
|
833
|
-
digitalWrite(14, LOW);
|
|
834
|
-
|
|
835
|
-
pinMode(BTN_SELECT_PIN, INPUT_PULLUP);
|
|
836
|
-
pinMode(BTN_UP_PIN, INPUT_PULLUP);
|
|
837
|
-
pinMode(BTN_DOWN_PIN, INPUT_PULLUP);
|
|
838
|
-
pinMode(POT_PIN, INPUT);
|
|
839
|
-
analogReadResolution(12);
|
|
840
|
-
|
|
841
|
-
homeIndex = readBucket(homeCount);
|
|
842
|
-
burnModeIndex = readBucket(BURN_COUNT);
|
|
843
|
-
playVolume = readPercent();
|
|
844
|
-
|
|
845
|
-
displayOk = display.begin(SSD1306_SWITCHCAPVCC, I2C_ADDRESS);
|
|
846
|
-
|
|
847
|
-
if (displayOk) {
|
|
848
|
-
Out.println("Display OK");
|
|
849
|
-
} else {
|
|
850
|
-
Out.println("Display FAILED");
|
|
851
|
-
delay(3000);
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
drawStandby();
|
|
855
|
-
lastMsgTime = millis();
|
|
856
|
-
lastInputTime = millis();
|
|
857
|
-
Out.println("DISCSTATION_READY");
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
void handleSelectPress(bool longPress) {
|
|
861
|
-
if (wakeDisplay()) { lastInputTime = millis(); return; } // wake-only press, swallow the action
|
|
862
|
-
lastInputTime = millis();
|
|
863
|
-
if (uiState == UI_HOME) {
|
|
864
|
-
if (longPress) {
|
|
865
|
-
Out.println("EJECT");
|
|
866
|
-
line1 = "Ejecting...";
|
|
867
|
-
line2 = "";
|
|
868
|
-
line3 = "";
|
|
869
|
-
returnToHomeAt = millis() + 5000;
|
|
870
|
-
drawStatus();
|
|
871
|
-
} else if (homeIndex >= 0 && homeIndex < homeCount) {
|
|
872
|
-
Out.print("SELECT:");
|
|
873
|
-
Out.println(homeModes[homeIndex]);
|
|
874
|
-
line1 = "Selected";
|
|
875
|
-
line2 = homeModes[homeIndex];
|
|
876
|
-
line3 = "";
|
|
877
|
-
drawStatus();
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
} else if (uiState == UI_STANDBY) {
|
|
881
|
-
Out.println("EJECT");
|
|
882
|
-
line1 = "Ejecting...";
|
|
883
|
-
line2 = "";
|
|
884
|
-
line3 = "";
|
|
885
|
-
returnToHomeAt = millis() + 5000;
|
|
886
|
-
drawStatus();
|
|
887
|
-
|
|
888
|
-
} else if (uiState == UI_STATUS && line1 == "Ejecting...") {
|
|
889
|
-
drawStandby();
|
|
890
|
-
|
|
891
|
-
} else if (uiState == UI_IP) {
|
|
892
|
-
if (longPress) {
|
|
893
|
-
Out.println("CANCEL");
|
|
894
|
-
drawHome();
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
} else if (uiState == UI_WAITING) {
|
|
898
|
-
if (longPress) {
|
|
899
|
-
Out.println("CANCEL");
|
|
900
|
-
drawHome();
|
|
901
|
-
} else {
|
|
902
|
-
Out.println("CONFIRM");
|
|
903
|
-
line1 = "Confirmed!";
|
|
904
|
-
line2 = "";
|
|
905
|
-
line3 = "";
|
|
906
|
-
drawStatus();
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
} else if (uiState == UI_BURN_READY) {
|
|
910
|
-
if (longPress) {
|
|
911
|
-
Out.print("START:");
|
|
912
|
-
Out.println(BURN_MODES[burnModeIndex]);
|
|
913
|
-
line1 = "Starting...";
|
|
914
|
-
line2 = BURN_MODES[burnModeIndex];
|
|
915
|
-
line3 = "";
|
|
916
|
-
drawStatus();
|
|
917
|
-
} else {
|
|
918
|
-
editingSpeed = !editingSpeed;
|
|
919
|
-
drawBurnReady();
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
} else if (uiState == UI_STATUS) {
|
|
923
|
-
if (longPress) {
|
|
924
|
-
Out.println("CANCEL");
|
|
925
|
-
line1 = "Cancelling...";
|
|
926
|
-
line2 = "";
|
|
927
|
-
line3 = "";
|
|
928
|
-
drawStatus();
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
} else if (uiState == UI_DISCONNECTED) {
|
|
932
|
-
Out.println("CANCEL");
|
|
933
|
-
drawStandby();
|
|
934
|
-
|
|
935
|
-
} else if (uiState == UI_PLAY) {
|
|
936
|
-
ffSpeedIndex = 0;
|
|
937
|
-
rewSpeedIndex = 0;
|
|
938
|
-
if (longPress) {
|
|
939
|
-
Out.println("PLAY_STOP");
|
|
940
|
-
} else {
|
|
941
|
-
Out.println("PLAY_BUTTON");
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
void handleUp(bool longPress) {
|
|
947
|
-
if (wakeDisplay()) { lastInputTime = millis(); return; } // wake-only press, swallow the action
|
|
948
|
-
lastInputTime = millis();
|
|
949
|
-
if (uiState == UI_HOME) {
|
|
950
|
-
if (homeCount > 0) {
|
|
951
|
-
homeIndex = (homeIndex - 1 + homeCount) % homeCount;
|
|
952
|
-
sendHomeMode();
|
|
953
|
-
drawHome();
|
|
954
|
-
}
|
|
955
|
-
} else if (uiState == UI_BURN_READY) {
|
|
956
|
-
if (editingSpeed) {
|
|
957
|
-
burnSpeedIndex = (burnSpeedIndex - 1 + SPEED_COUNT) % SPEED_COUNT;
|
|
958
|
-
Out.print("SPEED:");
|
|
959
|
-
Out.println(SPEED_MODES[burnSpeedIndex]);
|
|
960
|
-
drawBurnReady();
|
|
961
|
-
} else {
|
|
962
|
-
burnModeIndex = (burnModeIndex - 1 + BURN_COUNT) % BURN_COUNT;
|
|
963
|
-
sendBurnMode();
|
|
964
|
-
drawBurnReady();
|
|
965
|
-
}
|
|
966
|
-
} else if (uiState == UI_PLAY) {
|
|
967
|
-
if (audioPlayMode) {
|
|
968
|
-
Out.println(displayRotation == 0 ? (longPress ? "REW:BIG" : "REW:10") : (longPress ? "FF:BIG" : "FF:10"));
|
|
969
|
-
return;
|
|
970
|
-
}
|
|
971
|
-
rewSpeedIndex = 0;
|
|
972
|
-
if (longPress) {
|
|
973
|
-
Out.println("FF:BIG");
|
|
974
|
-
} else {
|
|
975
|
-
ffSpeedIndex = (ffSpeedIndex + 1) % 4;
|
|
976
|
-
int seek_sec[] = {10, 20, 30, 60};
|
|
977
|
-
Out.print("FF:");
|
|
978
|
-
Out.println(seek_sec[ffSpeedIndex]);
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
void handleDown(bool longPress) {
|
|
984
|
-
if (wakeDisplay()) { lastInputTime = millis(); return; } // wake-only press, swallow the action
|
|
985
|
-
lastInputTime = millis();
|
|
986
|
-
if (uiState == UI_HOME) {
|
|
987
|
-
if (homeCount > 0) {
|
|
988
|
-
homeIndex = (homeIndex + 1) % homeCount;
|
|
989
|
-
sendHomeMode();
|
|
990
|
-
drawHome();
|
|
991
|
-
}
|
|
992
|
-
} else if (uiState == UI_BURN_READY) {
|
|
993
|
-
if (editingSpeed) {
|
|
994
|
-
burnSpeedIndex = (burnSpeedIndex + 1) % SPEED_COUNT;
|
|
995
|
-
Out.print("SPEED:");
|
|
996
|
-
Out.println(SPEED_MODES[burnSpeedIndex]);
|
|
997
|
-
drawBurnReady();
|
|
998
|
-
} else {
|
|
999
|
-
burnModeIndex = (burnModeIndex + 1) % BURN_COUNT;
|
|
1000
|
-
sendBurnMode();
|
|
1001
|
-
drawBurnReady();
|
|
1002
|
-
}
|
|
1003
|
-
} else if (uiState == UI_PLAY) {
|
|
1004
|
-
if (audioPlayMode) {
|
|
1005
|
-
Out.println(displayRotation == 0 ? (longPress ? "FF:BIG" : "FF:10") : (longPress ? "REW:BIG" : "REW:10"));
|
|
1006
|
-
return;
|
|
1007
|
-
}
|
|
1008
|
-
ffSpeedIndex = 0;
|
|
1009
|
-
if (longPress) {
|
|
1010
|
-
Out.println("REW:BIG");
|
|
1011
|
-
} else {
|
|
1012
|
-
rewSpeedIndex = (rewSpeedIndex + 1) % 4;
|
|
1013
|
-
int seek_sec[] = {10, 20, 30, 60};
|
|
1014
|
-
Out.print("REW:");
|
|
1015
|
-
Out.println(seek_sec[rewSpeedIndex]);
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
void loop() {
|
|
1021
|
-
if (!wifiInitDone && millis() > 3000) {
|
|
1022
|
-
wifiInitDone = true;
|
|
1023
|
-
initWiFi();
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
if (credsJustSaved) {
|
|
1027
|
-
drawWifiReset();
|
|
1028
|
-
delay(600);
|
|
1029
|
-
ESP.restart();
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
if (portalActive) {
|
|
1033
|
-
wm.process();
|
|
1034
|
-
if (WiFi.status() == WL_CONNECTED) onWifiUp();
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
if (wifiConnected && WiFi.status() != WL_CONNECTED) {
|
|
1038
|
-
if (wifiDropAt == 0) wifiDropAt = millis();
|
|
1039
|
-
else if (millis() - wifiDropAt > WIFI_RETRY_MS) {
|
|
1040
|
-
wifiDropAt = millis();
|
|
1041
|
-
WiFi.disconnect();
|
|
1042
|
-
WiFi.begin();
|
|
1043
|
-
}
|
|
1044
|
-
} else if (wifiConnected) {
|
|
1045
|
-
wifiDropAt = 0;
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
if (wifiConnected && WiFi.status() == WL_CONNECTED) {
|
|
1049
|
-
if (otaEnabled) ArduinoOTA.handle();
|
|
1050
|
-
if (tcpServer.hasClient()) {
|
|
1051
|
-
if (tcpClient) tcpClient.stop();
|
|
1052
|
-
tcpClient = tcpServer.available();
|
|
1053
|
-
Out.setClient(&tcpClient);
|
|
1054
|
-
}
|
|
1055
|
-
if (tcpClient && !tcpClient.connected()) {
|
|
1056
|
-
tcpClient.stop();
|
|
1057
|
-
Out.setClient(nullptr);
|
|
1058
|
-
}
|
|
1059
|
-
if (tcpClient && tcpClient.available()) {
|
|
1060
|
-
String tmsg = tcpClient.readStringUntil('\n');
|
|
1061
|
-
if (tmsg.length() > 0) parseMessage(tmsg);
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
if (Serial.available()) {
|
|
1066
|
-
String msg = Serial.readStringUntil('\n');
|
|
1067
|
-
parseMessage(msg);
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
if (returnToHomeAt != 0 && (long)(millis() - returnToHomeAt) >= 0) {
|
|
1071
|
-
drawHome();
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
if (playStatusTemp && (long)(millis() - playStatusAt) >= 2500) {
|
|
1075
|
-
playStatusTemp = false;
|
|
1076
|
-
line1 = "PLAYING";
|
|
1077
|
-
drawPlay();
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
if (uiState == UI_PLAY && millis() - lastPotRead > POT_READ_MS) {
|
|
1081
|
-
lastPotRead = millis();
|
|
1082
|
-
int nextVolume = readPercent();
|
|
1083
|
-
if (abs(nextVolume - playVolume) >= 3) {
|
|
1084
|
-
playVolume = nextVolume;
|
|
1085
|
-
Out.print("POT:");
|
|
1086
|
-
Out.println(playVolume);
|
|
1087
|
-
drawPlay();
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
{
|
|
1092
|
-
bool sel = digitalRead(BTN_SELECT_PIN) == LOW;
|
|
1093
|
-
if (sel && !selectDown && millis() - selectLastDebounce > DEBOUNCE_MS) {
|
|
1094
|
-
selectDown = true;
|
|
1095
|
-
selectDownAt = millis();
|
|
1096
|
-
wifiResetArmed = false;
|
|
1097
|
-
}
|
|
1098
|
-
// 10s hold on HOME/SETUP/STANDBY = wipe Wi-Fi creds + reboot to portal.
|
|
1099
|
-
if (sel && selectDown && !wifiResetArmed &&
|
|
1100
|
-
(uiState == UI_HOME || uiState == UI_SETUP || uiState == UI_STANDBY) &&
|
|
1101
|
-
millis() - selectDownAt >= WIFI_RESET_HOLD_MS) {
|
|
1102
|
-
wifiResetArmed = true;
|
|
1103
|
-
Out.println("WiFi: creds wiped, rebooting");
|
|
1104
|
-
clearCreds();
|
|
1105
|
-
drawWifiReset();
|
|
1106
|
-
delay(800);
|
|
1107
|
-
ESP.restart();
|
|
1108
|
-
}
|
|
1109
|
-
if (!sel && selectDown) {
|
|
1110
|
-
selectDown = false;
|
|
1111
|
-
selectLastDebounce = millis();
|
|
1112
|
-
if (!wifiResetArmed) {
|
|
1113
|
-
handleSelectPress(millis() - selectDownAt >= LONG_PRESS_MS);
|
|
1114
|
-
}
|
|
1115
|
-
wifiResetArmed = false;
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
{
|
|
1120
|
-
bool up = digitalRead(BTN_UP_PIN) == LOW;
|
|
1121
|
-
if (up && !upDown && millis() - upLastDebounce > DEBOUNCE_MS) {
|
|
1122
|
-
upDown = true;
|
|
1123
|
-
upDownAt = millis();
|
|
1124
|
-
}
|
|
1125
|
-
if (!up && upDown) {
|
|
1126
|
-
upDown = false;
|
|
1127
|
-
upLastDebounce = millis();
|
|
1128
|
-
bool lp = millis() - upDownAt >= LONG_PRESS_MS;
|
|
1129
|
-
if (uiState == UI_PLAY || !lp) {
|
|
1130
|
-
handleUp(lp);
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
{
|
|
1136
|
-
bool dn = digitalRead(BTN_DOWN_PIN) == LOW;
|
|
1137
|
-
if (dn && !downDown && millis() - downLastDebounce > DEBOUNCE_MS) {
|
|
1138
|
-
downDown = true;
|
|
1139
|
-
downDownAt = millis();
|
|
1140
|
-
}
|
|
1141
|
-
if (!dn && downDown) {
|
|
1142
|
-
downDown = false;
|
|
1143
|
-
downLastDebounce = millis();
|
|
1144
|
-
bool lp = millis() - downDownAt >= LONG_PRESS_MS;
|
|
1145
|
-
if (uiState == UI_PLAY || !lp) {
|
|
1146
|
-
handleDown(lp);
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
if (uiState == UI_STATUS && progressPercent < 0 &&
|
|
1152
|
-
millis() - lastIndeterminateAnim > 500) {
|
|
1153
|
-
lastIndeterminateAnim = millis();
|
|
1154
|
-
indeterminateCount = (indeterminateCount % 4) + 1;
|
|
1155
|
-
if (!displayBlank) drawStatus();
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
if (uiState == UI_LOADING && millis() - lastLoadingAnim > 400) {
|
|
1159
|
-
lastLoadingAnim = millis();
|
|
1160
|
-
loadingDots = (loadingDots % 4) + 1;
|
|
1161
|
-
if (!displayBlank) drawLoading();
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
// --- Idle disc screensaver (HOME + STANDBY) ---
|
|
1165
|
-
if (displayOk && !displayBlank &&
|
|
1166
|
-
(uiState == UI_HOME || uiState == UI_STANDBY) &&
|
|
1167
|
-
(long)(millis() - lastInputTime) >= IDLE_BLANK_MS) {
|
|
1168
|
-
displayBlank = true;
|
|
1169
|
-
saverStep = 0;
|
|
1170
|
-
lastSaverFrame = 0;
|
|
1171
|
-
}
|
|
1172
|
-
if (displayBlank && displayOk &&
|
|
1173
|
-
(long)(millis() - lastSaverFrame) >= SAVER_FRAME_MS) {
|
|
1174
|
-
lastSaverFrame = millis();
|
|
1175
|
-
drawDiscSaver(saverStep);
|
|
1176
|
-
saverStep = (saverStep + 1) % 24;
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
if (uiState != UI_DISCONNECTED &&
|
|
1180
|
-
(long)(millis() - lastMsgTime) >= PING_TIMEOUT_MS) {
|
|
1181
|
-
drawDisconnected();
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
delay(20);
|
|
1185
|
-
}
|