Skip to content

Commit 338f505

Browse files
authored
Merge pull request #2 from Kotmin/dualMode
Dual mode: Getting quotes from Internet without serving Internet Access to end-users
2 parents 20c87d6 + 7f91cbd commit 338f505

3 files changed

Lines changed: 167 additions & 37 deletions

File tree

ViInvaders.ino

Lines changed: 88 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,73 @@
11
#include <ESPAsyncWebServer.h>
22
#include <AsyncTCP.h>
3-
4-
3+
#include <HTTPClient.h>
54
#include <WiFi.h>
65
#include <SPIFFS.h>
7-
86
#include <ArduinoJson.h>
97

8+
// External network (Internet)
9+
const char* ssid_sta = "NetworkName";
10+
const char* password_sta = "SecretPassword";
1011

12+
// Local AP (for players)
1113
const char* ssid = "ViInvaders";
1214
const char* password = "editorwar";
1315

14-
AsyncWebServer server(80);
16+
// static AP ip conf
17+
IPAddress apIP(192, 168, 4, 1);
18+
IPAddress apNetmask(255, 255, 255, 0);
19+
IPAddress apGateway(192, 168, 4, 1);
1520

16-
AsyncWebSocket ws("/ws"); // websocket endpoint
21+
// WebServer WebSocket
22+
AsyncWebServer server(80);
23+
AsyncWebSocket ws("/ws");
1724

18-
// joystick pinout
25+
// joystick pinout config
1926
#define JOY1_X 34
2027
#define JOY1_SW 25
2128
#define JOY2_X 35
2229
#define JOY2_SW 26
2330

31+
// quotes
32+
const char* quoteCachePath = "/quote.json";
33+
const char* quoteAPI = "https://api.quotable.io/quotes/random?limit=20&maxLength=120&tags=technology|motivational|famous-quotes";
34+
35+
2436
void notifyClients() {
2537
StaticJsonDocument<128> doc;
26-
2738
doc["j1x"] = analogRead(JOY1_X);
2839
doc["j1f"] = digitalRead(JOY1_SW) == LOW;
2940
doc["j2x"] = analogRead(JOY2_X);
3041
doc["j2f"] = digitalRead(JOY2_SW) == LOW;
31-
3242
String msg;
3343
serializeJson(doc, msg);
3444
ws.textAll(msg);
3545
}
3646

47+
// Get and cache quotes
48+
void fetchAndCacheQuotes() {
49+
HTTPClient http;
50+
http.begin(quoteAPI);
51+
int httpCode = http.GET();
52+
53+
if (httpCode == 200) {
54+
String payload = http.getString();
55+
File file = SPIFFS.open(quoteCachePath, FILE_WRITE);
56+
if (file) {
57+
file.print(payload);
58+
file.close();
59+
Serial.println("[Quote] 20 quotes cached.");
60+
} else {
61+
Serial.println("[Quote] Cannot write to file.");
62+
}
63+
} else {
64+
Serial.printf("[Quote] HTTP error: %d\n", httpCode);
65+
}
66+
67+
http.end();
68+
}
69+
70+
// WebSocket handle (for joystick communication)
3771
void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client,
3872
AwsEventType type, void *arg, uint8_t *data, size_t len) {
3973
switch (type) {
@@ -47,41 +81,71 @@ void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client,
4781
Serial.printf("[WS] Error on client %u\n", client->id());
4882
break;
4983
case WS_EVT_DATA:
50-
// Optional: handle incoming client messages here
51-
break;
52-
default:
5384
break;
5485
}
5586
}
5687

57-
5888
void setup() {
5989
Serial.begin(115200);
6090
pinMode(JOY1_SW, INPUT_PULLUP);
6191
pinMode(JOY2_SW, INPUT_PULLUP);
6292

6393
// Start SPIFFS
64-
if(!SPIFFS.begin(true)){
65-
Serial.println("Błąd SPIFFS");
94+
if (!SPIFFS.begin(true)) {
95+
Serial.println("[SPIFFS] Mount failed");
6696
return;
6797
}
6898

69-
// Access Point Mode
99+
// try to connect with external network
100+
WiFi.mode(WIFI_STA);
101+
WiFi.begin(ssid_sta, password_sta);
102+
Serial.print("[WiFi] Connecting to WAN");
103+
104+
for (int i = 0; i < 20 && WiFi.status() != WL_CONNECTED; ++i) {
105+
delay(300);
106+
Serial.print(".");
107+
}
108+
109+
if (WiFi.status() == WL_CONNECTED) {
110+
Serial.println("\n[WiFi] Connected to WAN:");
111+
Serial.println(WiFi.localIP());
112+
fetchAndCacheQuotes();
113+
} else {
114+
Serial.println("\n[WiFi] Failed to connect to Internet.");
115+
}
116+
117+
// Local AP
118+
WiFi.disconnect(true);
119+
delay(500);
120+
WiFi.mode(WIFI_AP);
121+
WiFi.softAPConfig(apIP, apGateway, apNetmask);
70122
WiFi.softAP(ssid, password);
71-
Serial.println("Access Point uruchomiony");
123+
124+
Serial.println("[AP] Access Point started");
125+
Serial.print("[AP] IP: ");
72126
Serial.println(WiFi.softAPIP());
73127

74128
// File serve
75129
server.serveStatic("/", SPIFFS, "/");
76-
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
130+
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
77131
request->send(SPIFFS, "/index.html", "text/html");
78132
});
79133

80-
// ws.onEvent([](AsyncWebSocket *server, AsyncWebSocketClient *client,
81-
// AwsEventType type, void *arg, uint8_t *data, size_t len) {
82-
// // optional: handle messages from clients
83-
// });
84-
134+
// Endpoint: quotes
135+
server.on("/quote", HTTP_GET, [](AsyncWebServerRequest *request) {
136+
if (SPIFFS.exists(quoteCachePath)) {
137+
request->send(SPIFFS, quoteCachePath, "application/json");
138+
} else {
139+
StaticJsonDocument<192> doc;
140+
doc["content"] = "No internet. Stay sharp, Commander!";
141+
doc["author"] = "ESP32";
142+
String out;
143+
serializeJson(doc, out);
144+
request->send(200, "application/json", out);
145+
}
146+
});
147+
148+
85149
ws.onEvent(onWsEvent);
86150
server.addHandler(&ws);
87151

@@ -94,5 +158,6 @@ void loop() {
94158
notifyClients();
95159
lastSend = millis();
96160
}
97-
ws.cleanupClients(); // rm unactive clients
161+
162+
ws.cleanupClients();
98163
}

data/game.js

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const fallbackQuotes = [
2626
{ text: "Code is like humor. When you have to explain it, it’s bad.", author: "Cory House" },
2727
{ text: "The best way to get a project done faster is to start sooner.", author: "Jim Highsmith" }
2828
];
29-
29+
/*
3030
async function getMotivationalQuote() {
3131
try {
3232
const res = await fetch("https://api.quotable.io/random?maxLength=120&tags=technology|motivational|famous-quotes");
@@ -40,7 +40,52 @@ async function getMotivationalQuote() {
4040
}
4141
}
4242
43+
*/
44+
45+
async function getMotivationalQuote() {
46+
// 1. Try use device connection
47+
try {
48+
const online = await fetch("https://api.quotable.io/random?maxLength=120&tags=technology|motivational|famous-quotes");
49+
if (online.ok) {
50+
const data = await online.json();
51+
quoteText = data.content || fallbackQuotes[0].text;
52+
quoteAuthor = data.author || fallbackQuotes[0].author;
53+
return;
54+
}
55+
} catch (_) {}
56+
57+
// 2. else ESP conn
58+
try {
59+
const local = await fetch("/quote");
60+
if (local.ok) {
61+
const data = await local.json();
62+
63+
// case 1: single quote object
64+
if (data.content && data.author) {
65+
quoteText = data.content;
66+
quoteAuthor = data.author;
67+
return;
68+
}
69+
70+
// case 2: array of quote objects
71+
if (Array.isArray(data) && data.length > 0) {
72+
const random = data[Math.floor(Math.random() * data.length)];
73+
if (random.content && random.author) {
74+
quoteText = random.content;
75+
quoteAuthor = random.author;
76+
return;
77+
}
78+
}
79+
}
80+
} catch (err) {
81+
console.warn("[Quote] Local ESP fetch failed:", err);
82+
}
4383

84+
// 3. else local code
85+
const fallback = fallbackQuotes[Math.floor(Math.random() * fallbackQuotes.length)];
86+
quoteText = fallback.text;
87+
quoteAuthor = fallback.author;
88+
}
4489

4590

4691
function resizeCanvas() {
@@ -473,6 +518,7 @@ canvas.addEventListener("touchstart", (e) => {
473518

474519
function setupWebSocket() {
475520
ws = new WebSocket("ws://" + location.hostname + "/ws");
521+
// ws = new WebSocket("ws://192.168.4.1/ws");
476522

477523
ws.onmessage = (event) => {
478524
const data = JSON.parse(event.data);

data/game.js.bak

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const fallbackQuotes = [
2626
{ text: "Code is like humor. When you have to explain it, it’s bad.", author: "Cory House" },
2727
{ text: "The best way to get a project done faster is to start sooner.", author: "Jim Highsmith" }
2828
];
29-
29+
/*
3030
async function getMotivationalQuote() {
3131
try {
3232
const res = await fetch("https://api.quotable.io/random?maxLength=120&tags=technology|motivational|famous-quotes");
@@ -40,7 +40,36 @@ async function getMotivationalQuote() {
4040
}
4141
}
4242

43+
*/
44+
45+
async function getMotivationalQuote() {
46+
// 1. Try use device connection
47+
try {
48+
const online = await fetch("https://api.quotable.io/random?maxLength=120&tags=technology|motivational|famous-quotes");
49+
if (online.ok) {
50+
const data = await online.json();
51+
quoteText = data.content || fallbackQuotes[0].text;
52+
quoteAuthor = data.author || fallbackQuotes[0].author;
53+
return;
54+
}
55+
} catch (_) {}
4356

57+
// 2. else ESP conn
58+
try {
59+
const local = await fetch("/quote");
60+
if (local.ok) {
61+
const data = await local.json();
62+
quoteText = data.content || fallbackQuotes[0].text;
63+
quoteAuthor = data.author || fallbackQuotes[0].author;
64+
return;
65+
}
66+
} catch (_) {}
67+
68+
// 3. else local code
69+
const fallback = fallbackQuotes[Math.floor(Math.random() * fallbackQuotes.length)];
70+
quoteText = fallback.text;
71+
quoteAuthor = fallback.author;
72+
}
4473

4574

4675
function resizeCanvas() {
@@ -343,20 +372,9 @@ function draw() {
343372
ctx.font = "20px sans-serif";
344373
ctx.fillText(`Score P1: ${player1.score} | P2: ${player2.score}`, canvas.width / 2 - 110, canvas.height / 2 + 20);
345374

346-
getMotivationalQuote();
347-
348375
ctx.fillText("Press shoot or move to restart", canvas.width / 2 - 150, canvas.height / 2 + 50);
349376

350377

351-
ctx.font = "18px sans-serif";
352-
const quoteWidth = ctx.measureText(`"${quoteText}"`).width;
353-
ctx.fillText(`"${quoteText}"`, (canvas.width - quoteWidth) / 2, canvas.height / 2 + 40);
354-
355-
ctx.font = "16px sans-serif";
356-
const authorWidth = ctx.measureText(`— ${quoteAuthor}`).width;
357-
ctx.fillText(`— ${quoteAuthor}`, (canvas.width - authorWidth) / 2, canvas.height / 2 + 65);
358-
359-
360378
requestAnimationFrame(draw);
361379
return;
362380
}
@@ -483,7 +501,8 @@ canvas.addEventListener("touchstart", (e) => {
483501

484502

485503
function setupWebSocket() {
486-
ws = new WebSocket("ws://" + location.hostname + "/ws");
504+
// ws = new WebSocket("ws://" + location.hostname + "/ws");
505+
ws = new WebSocket("ws://192.168.4.1/ws");
487506

488507
ws.onmessage = (event) => {
489508
const data = JSON.parse(event.data);

0 commit comments

Comments
 (0)