discstation 0.1.22 → 0.1.26
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 +43 -8
- package/arduino/c6/DiscStation_C6.ino +293 -41
- package/arduino/c6/secrets.h.example +14 -0
- package/arduino/v1/DiscStation.ino +214 -19
- package/arduino/v1/secrets.h.example +14 -0
- package/discstation.env.example +7 -0
- package/package.json +1 -1
- package/requirements.txt +1 -0
- package/scripts/lib/net.mjs +14 -0
- package/scripts/open.mjs +2 -0
- package/scripts/setup.mjs +4 -1
- package/src/discstation.py +150 -15
- package/src/discstation_burn.py +9 -0
- package/src/discstation_host.py +73 -0
|
@@ -3,9 +3,20 @@
|
|
|
3
3
|
#include <Adafruit_SSD1306.h>
|
|
4
4
|
#include "esp_task_wdt.h"
|
|
5
5
|
#include <WiFi.h>
|
|
6
|
+
#include "esp_mac.h"
|
|
7
|
+
#include <WiFiManager.h>
|
|
8
|
+
#include <ESPmDNS.h>
|
|
9
|
+
#include <Preferences.h>
|
|
6
10
|
#include <ArduinoOTA.h>
|
|
7
11
|
#include <QRCode.h>
|
|
8
12
|
|
|
13
|
+
// Optional build-time Wi-Fi override for power users: copy secrets.h.example
|
|
14
|
+
// to secrets.h (git-ignored) and set WIFI_SSID / WIFI_PASS / OTA_PASSWORD.
|
|
15
|
+
// If WIFI_SSID is defined the runtime setup portal is skipped entirely.
|
|
16
|
+
#if __has_include("secrets.h")
|
|
17
|
+
#include "secrets.h"
|
|
18
|
+
#endif
|
|
19
|
+
|
|
9
20
|
#define SCREEN_WIDTH 128
|
|
10
21
|
#define SCREEN_HEIGHT 64
|
|
11
22
|
#define OLED_RESET -1
|
|
@@ -23,9 +34,14 @@
|
|
|
23
34
|
#define POT_READ_MS 200
|
|
24
35
|
#define DONE_RESET_MS 30000
|
|
25
36
|
#define STANDBY_BLANK_MS 60000
|
|
37
|
+
#define IDLE_BLANK_MS 45000 // blank the OLED after this long with no input on HOME/STANDBY
|
|
26
38
|
#define PING_TIMEOUT_MS 30000
|
|
27
39
|
#define LED_BLINK_MS 250
|
|
28
40
|
|
|
41
|
+
#define WIFI_RESET_HOLD_MS 10000 // hold SELECT this long on HOME to wipe Wi-Fi creds
|
|
42
|
+
#define WIFI_CONNECT_TIMEOUT_MS 18000 // give a stored-creds join this long before falling to the portal
|
|
43
|
+
#define WIFI_RETRY_MS 30000 // if a live link drops, force a re-join after this
|
|
44
|
+
|
|
29
45
|
#define MAX_HOME_MODES 5
|
|
30
46
|
#define BURN_COUNT 4
|
|
31
47
|
#define SPEED_COUNT 4
|
|
@@ -36,8 +52,17 @@ String homeModes[MAX_HOME_MODES] = {"BURN", "PLAY", "RIP"};
|
|
|
36
52
|
|
|
37
53
|
WiFiServer tcpServer(TCP_PORT);
|
|
38
54
|
WiFiClient tcpClient;
|
|
55
|
+
WiFiManager wm;
|
|
56
|
+
Preferences prefs;
|
|
39
57
|
bool wifiConnected = false;
|
|
40
58
|
bool wifiInitDone = false;
|
|
59
|
+
bool portalActive = false;
|
|
60
|
+
bool otaEnabled = false;
|
|
61
|
+
bool credsJustSaved = false;
|
|
62
|
+
bool wifiResetArmed = false;
|
|
63
|
+
unsigned long wifiDropAt = 0;
|
|
64
|
+
String apName = "DiscStation";
|
|
65
|
+
String mdnsHost = "discstation";
|
|
41
66
|
|
|
42
67
|
class DualPrint : public Print {
|
|
43
68
|
public:
|
|
@@ -56,23 +81,105 @@ private:
|
|
|
56
81
|
WiFiClient* _client = nullptr;
|
|
57
82
|
} Out;
|
|
58
83
|
|
|
84
|
+
void drawSetup(); // fwd decls (defined with the other draw* below)
|
|
85
|
+
void drawWifiReset();
|
|
86
|
+
|
|
87
|
+
String deviceSuffix() {
|
|
88
|
+
uint8_t mac[6];
|
|
89
|
+
esp_read_mac(mac, ESP_MAC_WIFI_STA); // factory MAC from eFuse - valid before the Wi-Fi driver starts
|
|
90
|
+
char buf[5];
|
|
91
|
+
snprintf(buf, sizeof(buf), "%02X%02X", mac[4], mac[5]);
|
|
92
|
+
return String(buf);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
void loadCreds(String& ssid, String& pass) {
|
|
96
|
+
prefs.begin("discstation", true);
|
|
97
|
+
ssid = prefs.getString("ssid", "");
|
|
98
|
+
pass = prefs.getString("pass", "");
|
|
99
|
+
prefs.end();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
void saveCreds(const String& ssid, const String& pass) {
|
|
103
|
+
prefs.begin("discstation", false);
|
|
104
|
+
prefs.putString("ssid", ssid);
|
|
105
|
+
prefs.putString("pass", pass);
|
|
106
|
+
prefs.end();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
void clearCreds() {
|
|
110
|
+
prefs.begin("discstation", false);
|
|
111
|
+
prefs.clear();
|
|
112
|
+
prefs.end();
|
|
113
|
+
WiFi.disconnect(true, true); // also wipe the ESP's own persisted creds
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
void wmSaveCallback() {
|
|
117
|
+
saveCreds(wm.getWiFiSSID(), wm.getWiFiPass());
|
|
118
|
+
credsJustSaved = true; // loop() reboots cleanly into STA mode
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
void onWifiUp() {
|
|
122
|
+
portalActive = false;
|
|
123
|
+
wifiConnected = true;
|
|
124
|
+
wifiDropAt = 0;
|
|
125
|
+
WiFi.setSleep(WIFI_PS_MIN_MODEM);
|
|
126
|
+
tcpServer.begin();
|
|
127
|
+
if (MDNS.begin(mdnsHost.c_str())) {
|
|
128
|
+
MDNS.addService("discstation", "tcp", TCP_PORT);
|
|
129
|
+
}
|
|
130
|
+
#ifdef OTA_PASSWORD
|
|
131
|
+
ArduinoOTA.setHostname(mdnsHost.c_str());
|
|
132
|
+
ArduinoOTA.setPassword(OTA_PASSWORD);
|
|
133
|
+
ArduinoOTA.begin();
|
|
134
|
+
otaEnabled = true;
|
|
135
|
+
#endif
|
|
136
|
+
Out.print("WiFi OK "); Out.println(WiFi.localIP());
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
void startPortal() {
|
|
140
|
+
Out.print("WiFi: setup portal '"); Out.print(apName); Out.println("'");
|
|
141
|
+
portalActive = true;
|
|
142
|
+
WiFi.mode(WIFI_AP_STA);
|
|
143
|
+
wm.setConfigPortalBlocking(false);
|
|
144
|
+
wm.setConfigPortalTimeout(0);
|
|
145
|
+
wm.setSaveConfigCallback(wmSaveCallback);
|
|
146
|
+
wm.startConfigPortal(apName.c_str()); // open AP at 192.168.4.1
|
|
147
|
+
drawSetup();
|
|
148
|
+
}
|
|
149
|
+
|
|
59
150
|
void initWiFi() {
|
|
60
|
-
|
|
151
|
+
apName = "DiscStation-" + deviceSuffix();
|
|
152
|
+
mdnsHost = "discstation-" + deviceSuffix();
|
|
153
|
+
mdnsHost.toLowerCase();
|
|
154
|
+
|
|
61
155
|
WiFi.mode(WIFI_STA);
|
|
62
|
-
WiFi.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
156
|
+
WiFi.setAutoReconnect(true);
|
|
157
|
+
WiFi.setSleep(WIFI_PS_MIN_MODEM);
|
|
158
|
+
|
|
159
|
+
String ssid, pass;
|
|
160
|
+
#ifdef WIFI_SSID
|
|
161
|
+
ssid = WIFI_SSID;
|
|
162
|
+
pass = WIFI_PASS;
|
|
163
|
+
#else
|
|
164
|
+
loadCreds(ssid, pass);
|
|
165
|
+
#endif
|
|
166
|
+
|
|
167
|
+
if (ssid.length() > 0) {
|
|
168
|
+
Out.print("WiFi "); Out.print(ssid); Out.print("...");
|
|
169
|
+
WiFi.begin(ssid.c_str(), pass.c_str());
|
|
170
|
+
unsigned long t0 = millis();
|
|
171
|
+
while (WiFi.status() != WL_CONNECTED && millis() - t0 < WIFI_CONNECT_TIMEOUT_MS) {
|
|
172
|
+
esp_task_wdt_reset();
|
|
173
|
+
delay(200);
|
|
174
|
+
}
|
|
66
175
|
}
|
|
176
|
+
|
|
67
177
|
if (WiFi.status() == WL_CONNECTED) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
ArduinoOTA.begin();
|
|
71
|
-
ArduinoOTA.setHostname("discstation-v1");
|
|
72
|
-
ArduinoOTA.setPassword("dvdstation");
|
|
73
|
-
Out.print(" OK "); Out.println(WiFi.localIP());
|
|
178
|
+
Out.println(" OK");
|
|
179
|
+
onWifiUp();
|
|
74
180
|
} else {
|
|
75
|
-
Out.println(" fail");
|
|
181
|
+
if (ssid.length() > 0) Out.println(" fail (2.4GHz band / wrong password?)");
|
|
182
|
+
startPortal();
|
|
76
183
|
}
|
|
77
184
|
}
|
|
78
185
|
String discName = "";
|
|
@@ -89,7 +196,8 @@ enum UiState {
|
|
|
89
196
|
UI_STANDBY,
|
|
90
197
|
UI_WAITING,
|
|
91
198
|
UI_IP,
|
|
92
|
-
UI_DISCONNECTED
|
|
199
|
+
UI_DISCONNECTED,
|
|
200
|
+
UI_SETUP
|
|
93
201
|
};
|
|
94
202
|
|
|
95
203
|
const char* BURN_MODES[BURN_COUNT] = {"AUTO", "BEST", "LONG", "TEST"};
|
|
@@ -143,6 +251,7 @@ bool editingSpeed = false;
|
|
|
143
251
|
int playVolume = 50;
|
|
144
252
|
unsigned long standbyStartTime = 0;
|
|
145
253
|
unsigned long lastMsgTime = 0;
|
|
254
|
+
unsigned long lastInputTime = 0; // last button press / screen change; drives OLED idle-blank
|
|
146
255
|
bool displayBlank = false;
|
|
147
256
|
bool audioPlayMode = false;
|
|
148
257
|
int displayRotation = 0;
|
|
@@ -210,6 +319,7 @@ void drawHeader() {
|
|
|
210
319
|
void drawHome() {
|
|
211
320
|
uiState = UI_HOME;
|
|
212
321
|
returnToHomeAt = 0;
|
|
322
|
+
lastInputTime = millis();
|
|
213
323
|
if (!displayOk) return;
|
|
214
324
|
|
|
215
325
|
display.clearDisplay();
|
|
@@ -360,9 +470,13 @@ void drawIP() {
|
|
|
360
470
|
}
|
|
361
471
|
|
|
362
472
|
void drawStandby() {
|
|
473
|
+
// While the Wi-Fi setup portal is up and the appliance is otherwise idle,
|
|
474
|
+
// the standby screen doubles as the setup instructions.
|
|
475
|
+
if (portalActive) { drawSetup(); return; }
|
|
363
476
|
uiState = UI_STANDBY;
|
|
364
477
|
returnToHomeAt = 0;
|
|
365
478
|
standbyStartTime = millis();
|
|
479
|
+
lastInputTime = millis();
|
|
366
480
|
displayBlank = false;
|
|
367
481
|
if (!displayOk) return;
|
|
368
482
|
|
|
@@ -375,6 +489,39 @@ void drawStandby() {
|
|
|
375
489
|
display.display();
|
|
376
490
|
}
|
|
377
491
|
|
|
492
|
+
void drawSetup() {
|
|
493
|
+
uiState = UI_SETUP;
|
|
494
|
+
returnToHomeAt = 0;
|
|
495
|
+
displayBlank = false;
|
|
496
|
+
lastInputTime = millis();
|
|
497
|
+
if (!displayOk) return;
|
|
498
|
+
|
|
499
|
+
display.clearDisplay();
|
|
500
|
+
drawHeader();
|
|
501
|
+
display.setCursor(2, 16);
|
|
502
|
+
display.print("WIFI SETUP");
|
|
503
|
+
display.setCursor(2, 28);
|
|
504
|
+
display.print("JOIN:");
|
|
505
|
+
display.setCursor(2, 38);
|
|
506
|
+
printUpper(apName);
|
|
507
|
+
display.setCursor(2, 50);
|
|
508
|
+
display.print("THEN 192.168.4.1");
|
|
509
|
+
display.display();
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
void drawWifiReset() {
|
|
513
|
+
uiState = UI_SETUP;
|
|
514
|
+
displayBlank = false;
|
|
515
|
+
if (!displayOk) return;
|
|
516
|
+
display.clearDisplay();
|
|
517
|
+
drawHeader();
|
|
518
|
+
display.setCursor(2, 25);
|
|
519
|
+
display.print("WIFI RESET");
|
|
520
|
+
display.setCursor(2, 40);
|
|
521
|
+
display.print("REBOOTING...");
|
|
522
|
+
display.display();
|
|
523
|
+
}
|
|
524
|
+
|
|
378
525
|
void drawDisconnected() {
|
|
379
526
|
uiState = UI_DISCONNECTED;
|
|
380
527
|
returnToHomeAt = 0;
|
|
@@ -403,6 +550,7 @@ void wakeDisplay() {
|
|
|
403
550
|
case UI_WAITING: drawWaiting(); break;
|
|
404
551
|
case UI_STANDBY: drawStandby(); break;
|
|
405
552
|
case UI_DISCONNECTED: drawDisconnected(); break;
|
|
553
|
+
case UI_SETUP: drawSetup(); break;
|
|
406
554
|
}
|
|
407
555
|
}
|
|
408
556
|
|
|
@@ -602,6 +750,7 @@ void parseMessage(String msg) {
|
|
|
602
750
|
}
|
|
603
751
|
|
|
604
752
|
void setup() {
|
|
753
|
+
setCpuFrequencyMhz(160); // 240 -> 160: ~halves CPU power, Wi-Fi/I2C/OTA all fine at 160
|
|
605
754
|
Serial.begin(115200);
|
|
606
755
|
esp_task_wdt_add(NULL);
|
|
607
756
|
Wire.begin(21, 22);
|
|
@@ -639,11 +788,13 @@ void setup() {
|
|
|
639
788
|
|
|
640
789
|
drawStandby();
|
|
641
790
|
lastMsgTime = millis();
|
|
791
|
+
lastInputTime = millis();
|
|
642
792
|
Out.println("DISCSTATION_READY");
|
|
643
793
|
}
|
|
644
794
|
|
|
645
795
|
void handleSelectPress(bool longPress) {
|
|
646
796
|
wakeDisplay();
|
|
797
|
+
lastInputTime = millis();
|
|
647
798
|
if (uiState == UI_HOME) {
|
|
648
799
|
if (longPress) {
|
|
649
800
|
Out.println("EJECT");
|
|
@@ -733,6 +884,7 @@ void handleSelectPress(bool longPress) {
|
|
|
733
884
|
|
|
734
885
|
void handleUp(bool longPress) {
|
|
735
886
|
wakeDisplay();
|
|
887
|
+
lastInputTime = millis();
|
|
736
888
|
if (uiState == UI_HOME) {
|
|
737
889
|
if (homeCount > 0) {
|
|
738
890
|
homeIndex = (homeIndex - 1 + homeCount) % homeCount;
|
|
@@ -772,6 +924,7 @@ void handleUp(bool longPress) {
|
|
|
772
924
|
|
|
773
925
|
void handleDown(bool longPress) {
|
|
774
926
|
wakeDisplay();
|
|
927
|
+
lastInputTime = millis();
|
|
775
928
|
if (uiState == UI_HOME) {
|
|
776
929
|
if (homeCount > 0) {
|
|
777
930
|
homeIndex = (homeIndex + 1) % homeCount;
|
|
@@ -814,8 +967,33 @@ void loop() {
|
|
|
814
967
|
wifiInitDone = true;
|
|
815
968
|
initWiFi();
|
|
816
969
|
}
|
|
817
|
-
|
|
818
|
-
|
|
970
|
+
|
|
971
|
+
if (credsJustSaved) { // portal saved new creds -> reboot into clean STA
|
|
972
|
+
drawWifiReset();
|
|
973
|
+
delay(600);
|
|
974
|
+
ESP.restart();
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
if (portalActive) {
|
|
978
|
+
wm.process();
|
|
979
|
+
if (WiFi.status() == WL_CONNECTED) onWifiUp();
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// Live link dropped: WiFi.setAutoReconnect handles most; if still down after
|
|
983
|
+
// WIFI_RETRY_MS, force a fresh join.
|
|
984
|
+
if (wifiConnected && WiFi.status() != WL_CONNECTED) {
|
|
985
|
+
if (wifiDropAt == 0) wifiDropAt = millis();
|
|
986
|
+
else if (millis() - wifiDropAt > WIFI_RETRY_MS) {
|
|
987
|
+
wifiDropAt = millis();
|
|
988
|
+
WiFi.disconnect();
|
|
989
|
+
WiFi.begin();
|
|
990
|
+
}
|
|
991
|
+
} else if (wifiConnected) {
|
|
992
|
+
wifiDropAt = 0;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
if (wifiConnected && WiFi.status() == WL_CONNECTED) {
|
|
996
|
+
if (otaEnabled) ArduinoOTA.handle();
|
|
819
997
|
if (tcpServer.hasClient()) {
|
|
820
998
|
if (tcpClient) tcpClient.stop();
|
|
821
999
|
tcpClient = tcpServer.available();
|
|
@@ -865,11 +1043,27 @@ void loop() {
|
|
|
865
1043
|
if (sel && !selectDown && millis() - selectLastDebounce > DEBOUNCE_MS) {
|
|
866
1044
|
selectDown = true;
|
|
867
1045
|
selectDownAt = millis();
|
|
1046
|
+
wifiResetArmed = false;
|
|
1047
|
+
}
|
|
1048
|
+
// 10s hold on HOME (or SETUP) = wipe Wi-Fi creds + reboot to portal. Fires
|
|
1049
|
+
// while still held so the eventual release doesn't also trigger EJECT.
|
|
1050
|
+
if (sel && selectDown && !wifiResetArmed &&
|
|
1051
|
+
(uiState == UI_HOME || uiState == UI_SETUP || uiState == UI_STANDBY) &&
|
|
1052
|
+
millis() - selectDownAt >= WIFI_RESET_HOLD_MS) {
|
|
1053
|
+
wifiResetArmed = true;
|
|
1054
|
+
Out.println("WiFi: creds wiped, rebooting");
|
|
1055
|
+
clearCreds();
|
|
1056
|
+
drawWifiReset();
|
|
1057
|
+
delay(800);
|
|
1058
|
+
ESP.restart();
|
|
868
1059
|
}
|
|
869
1060
|
if (!sel && selectDown) {
|
|
870
1061
|
selectDown = false;
|
|
871
1062
|
selectLastDebounce = millis();
|
|
872
|
-
|
|
1063
|
+
if (!wifiResetArmed) {
|
|
1064
|
+
handleSelectPress(millis() - selectDownAt >= LONG_PRESS_MS);
|
|
1065
|
+
}
|
|
1066
|
+
wifiResetArmed = false;
|
|
873
1067
|
}
|
|
874
1068
|
}
|
|
875
1069
|
|
|
@@ -915,9 +1109,10 @@ void loop() {
|
|
|
915
1109
|
if (!displayBlank) drawStatus();
|
|
916
1110
|
}
|
|
917
1111
|
|
|
918
|
-
// ---
|
|
919
|
-
if (displayOk &&
|
|
920
|
-
(
|
|
1112
|
+
// --- OLED idle blanking (HOME + STANDBY) ---
|
|
1113
|
+
if (displayOk && !displayBlank &&
|
|
1114
|
+
(uiState == UI_HOME || uiState == UI_STANDBY) &&
|
|
1115
|
+
(long)(millis() - lastInputTime) >= IDLE_BLANK_MS) {
|
|
921
1116
|
display.ssd1306_command(0xAE);
|
|
922
1117
|
displayBlank = true;
|
|
923
1118
|
digitalWrite(LED_POWER_PIN, LOW);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Optional build-time Wi-Fi override for the ESP32 DevKit remote.
|
|
2
|
+
//
|
|
3
|
+
// cp secrets.h.example secrets.h (secrets.h is git-ignored)
|
|
4
|
+
//
|
|
5
|
+
// If WIFI_SSID is defined, the firmware connects straight to this network and
|
|
6
|
+
// the runtime setup portal is skipped entirely. Leave secrets.h absent to use
|
|
7
|
+
// the normal captive-portal provisioning ("DiscStation-XXXX" AP).
|
|
8
|
+
|
|
9
|
+
#define WIFI_SSID "YourNetwork"
|
|
10
|
+
#define WIFI_PASS "YourPassword"
|
|
11
|
+
|
|
12
|
+
// Optional: enable Arduino OTA updates with this password. Omit to keep OTA off
|
|
13
|
+
// (recommended unless you actually use it).
|
|
14
|
+
// #define OTA_PASSWORD "change-me"
|
package/discstation.env.example
CHANGED
|
@@ -7,6 +7,13 @@ YTDLP_FRAGMENT_RETRIES=3
|
|
|
7
7
|
DISC_OUTPUT_LIMIT_BYTES=4300000000
|
|
8
8
|
DISC_DL_OUTPUT_LIMIT_BYTES=8000000000
|
|
9
9
|
|
|
10
|
+
# Wi-Fi appliance remote. Default (unset) = "auto": if no USB cable is
|
|
11
|
+
# present, find the remote by mDNS / discstation.local. Set an IP or
|
|
12
|
+
# hostname to skip discovery (connects on TCP 2323); set "off" to never
|
|
13
|
+
# look. The remote's OLED shows its IP after you provision its Wi-Fi.
|
|
14
|
+
# DISC_REMOTE_HOST=192.168.1.50
|
|
15
|
+
# DISC_REMOTE_HOST=off
|
|
16
|
+
|
|
10
17
|
# TMDb API key for video (DVD / burned movie) metadata lookup. Get a free key at
|
|
11
18
|
# https://www.themoviedb.org/settings/api . Without it, ripped videos keep their
|
|
12
19
|
# disc-label / filename naming. Alternatively put the key in
|
package/package.json
CHANGED
package/requirements.txt
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { networkInterfaces } from 'node:os';
|
|
2
|
+
|
|
3
|
+
/** First non-internal IPv4 LAN address, so other devices on the network know
|
|
4
|
+
* what to open — mirrors discstation.py's local_ip(). Falls back to
|
|
5
|
+
* 'localhost' if nothing's found (e.g. no network connection). */
|
|
6
|
+
export function lanIp() {
|
|
7
|
+
const nets = networkInterfaces();
|
|
8
|
+
for (const name of Object.keys(nets)) {
|
|
9
|
+
for (const net of nets[name] || []) {
|
|
10
|
+
if (net.family === 'IPv4' && !net.internal) return net.address;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return 'localhost';
|
|
14
|
+
}
|
package/scripts/open.mjs
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
import { get } from 'node:http';
|
|
8
|
+
import { lanIp } from './lib/net.mjs';
|
|
8
9
|
|
|
9
10
|
const port = process.env.DISCSTATION_HTTP_PORT || '8081';
|
|
10
11
|
const url = process.argv[2] || `http://localhost:${port}/`;
|
|
@@ -24,6 +25,7 @@ function open(target) {
|
|
|
24
25
|
// Best-effort liveness check so a stopped host gives a clear hint.
|
|
25
26
|
const probe = get(url, { timeout: 1500 }, (res) => {
|
|
26
27
|
res.resume();
|
|
28
|
+
console.log(`Also reachable at http://${lanIp()}:${port}/ from any phone/computer on your network.`);
|
|
27
29
|
open(url);
|
|
28
30
|
});
|
|
29
31
|
probe.on('timeout', () => probe.destroy());
|
package/scripts/setup.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
8
8
|
import { existsSync } from 'node:fs';
|
|
9
9
|
import { dirname, join, resolve } from 'node:path';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { lanIp } from './lib/net.mjs';
|
|
11
12
|
|
|
12
13
|
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
13
14
|
const args = process.argv.slice(2);
|
|
@@ -45,9 +46,11 @@ function run(cmd, cmdArgs) {
|
|
|
45
46
|
}
|
|
46
47
|
if ((r.status ?? 1) === 0) {
|
|
47
48
|
const port = process.env.DISCSTATION_HTTP_PORT || '8081';
|
|
49
|
+
const ip = lanIp();
|
|
48
50
|
console.log(
|
|
49
51
|
`\nDiscStation is installed and running.\n` +
|
|
50
|
-
` Web UI: http
|
|
52
|
+
` Web UI: http://${ip}:${port}/ (run "discstation" to open it any time)\n` +
|
|
53
|
+
` Open that address from any phone/computer on your network too.\n` +
|
|
51
54
|
` Install it as an app from the browser menu, or the in-page INSTALL APP button.\n`,
|
|
52
55
|
);
|
|
53
56
|
spawnSync(process.execPath, [join(ROOT, 'scripts', 'open.mjs')], { stdio: 'ignore' });
|