Skip to content

Commit 990dd79

Browse files
Added Lan Gallery project for Inkplate 10
1 parent 698af7f commit 990dd79

File tree

2 files changed

+430
-0
lines changed

2 files changed

+430
-0
lines changed
Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
#if !defined(ARDUINO_INKPLATE10) && !defined(ARDUINO_INKPLATE10V2)
2+
#error "Select 'Soldered Inkplate10' in the boards menu."
3+
#endif
4+
5+
#include "Inkplate.h"
6+
7+
Inkplate display(INKPLATE_3BIT);
8+
9+
const char* ssid = "YOUR_SSID";
10+
const char* password = "YOUR_PASSWORD";
11+
12+
#define IMAGE_CHANGE_INTERVAL 30000UL
13+
14+
struct Node { char* path; int id; Node* next; };
15+
Node* head = nullptr;
16+
int nodeCount = 0;
17+
18+
SemaphoreHandle_t sdMutex;
19+
SdFile* currentUploadFile = nullptr;
20+
volatile bool uploadComplete = false;
21+
22+
void setupWebServer();
23+
24+
void addNode(const char* p, int id) {
25+
Node* n = (Node*)malloc(sizeof(Node));
26+
if (!n) return;
27+
n->path = strdup(p);
28+
n->id = id;
29+
if (!head) {
30+
head = n;
31+
n->next = head;
32+
} else {
33+
Node* t = head;
34+
while (t->next != head) t = t->next;
35+
t->next = n;
36+
n->next = head;
37+
}
38+
nodeCount++;
39+
}
40+
41+
bool buildImageList() {
42+
Serial.println("Building image list...");
43+
if (!display.sdCardInit()) {
44+
Serial.println("SD Card init failed!");
45+
return false;
46+
}
47+
SdFile root;
48+
if (!root.open("/")) {
49+
Serial.println("Failed to open root directory!");
50+
return false;
51+
}
52+
53+
if (head) {
54+
Node* current = head;
55+
Node* first = head;
56+
do {
57+
Node* next = current->next;
58+
free(current->path);
59+
free(current);
60+
current = next;
61+
} while (current != first);
62+
head = nullptr;
63+
}
64+
nodeCount = 0;
65+
66+
SdFile e;
67+
while (e.openNext(&root, O_RDONLY)) {
68+
char name[64];
69+
e.getName(name, sizeof(name));
70+
String nm(name);
71+
nm.toLowerCase();
72+
if (nm.endsWith(".bmp") || nm.endsWith(".jpg") || nm.endsWith(".jpeg")) {
73+
Serial.printf("Found image: %s\n", name);
74+
addNode(name, nodeCount);
75+
}
76+
e.close();
77+
}
78+
root.close();
79+
Serial.printf("Total images found: %d\n", nodeCount);
80+
return nodeCount > 0;
81+
}
82+
83+
Node* pickRandomNode() {
84+
if (!head) return nullptr;
85+
int id = random(nodeCount);
86+
Node* t = head;
87+
while (t->id != id) t = t->next;
88+
return t;
89+
}
90+
91+
static bool readBmpSize_SdFat(const char* path, int &w, int &h) {
92+
w = h = 0;
93+
SdFile f;
94+
if (!f.open(path, O_RDONLY)) return false;
95+
96+
uint8_t hdr[26];
97+
int n = f.read(hdr, sizeof(hdr));
98+
f.close();
99+
if (n < 26) return false;
100+
if (hdr[0] != 'B' || hdr[1] != 'M') return false;
101+
102+
int32_t bw = (int32_t)hdr[18] | ((int32_t)hdr[19] << 8) | ((int32_t)hdr[20] << 16) | ((int32_t)hdr[21] << 24);
103+
int32_t bh = (int32_t)hdr[22] | ((int32_t)hdr[23] << 8) | ((int32_t)hdr[24] << 16) | ((int32_t)hdr[25] << 24);
104+
if (bw <= 0 || bh == 0) return false;
105+
w = (int)bw;
106+
h = (int)((bh < 0) ? -bh : bh);
107+
return true;
108+
}
109+
110+
static bool readJpegSize_SdFat(const char* path, int &w, int &h) {
111+
w = h = 0;
112+
SdFile f;
113+
if (!f.open(path, O_RDONLY)) return false;
114+
115+
uint8_t b[2];
116+
if (f.read(b, 2) != 2 || b[0] != 0xFF || b[1] != 0xD8) { f.close(); return false; }
117+
118+
const size_t SKIP_BUF = 64;
119+
uint8_t skipBuf[SKIP_BUF];
120+
121+
auto readU8 = [&](uint8_t &out)->bool { return f.read(&out, 1) == 1; };
122+
auto readU16 = [&](uint16_t &out)->bool {
123+
uint8_t hi, lo;
124+
if (f.read(&hi,1) != 1 || f.read(&lo,1) != 1) return false;
125+
out = ((uint16_t)hi << 8) | lo;
126+
return true;
127+
};
128+
auto skipN = [&](uint32_t n)->bool {
129+
while (n > 0) {
130+
size_t chunk = (n > SKIP_BUF) ? SKIP_BUF : n;
131+
int r = f.read(skipBuf, chunk);
132+
if (r != (int)chunk) return false;
133+
n -= chunk;
134+
}
135+
return true;
136+
};
137+
138+
while (true) {
139+
uint8_t markerPrefix;
140+
do {
141+
if (!readU8(markerPrefix)) { f.close(); return false; }
142+
} while (markerPrefix != 0xFF);
143+
144+
uint8_t marker;
145+
do {
146+
if (!readU8(marker)) { f.close(); return false; }
147+
} while (marker == 0xFF);
148+
149+
if (marker == 0xD9) break;
150+
if (marker == 0xD8 || marker == 0x01) continue;
151+
152+
uint16_t segLen;
153+
if (!readU16(segLen)) { f.close(); return false; }
154+
if (segLen < 2) { f.close(); return false; }
155+
156+
bool isSOF =
157+
(marker >= 0xC0 && marker <= 0xC3) ||
158+
(marker >= 0xC5 && marker <= 0xC7) ||
159+
(marker >= 0xC9 && marker <= 0xCB) ||
160+
(marker >= 0xCD && marker <= 0xCF);
161+
162+
if (isSOF) {
163+
uint8_t P, Hh, Hl, Wh, Wl;
164+
if (!readU8(P) || !readU8(Hh) || !readU8(Hl) || !readU8(Wh) || !readU8(Wl)) { f.close(); return false; }
165+
h = ((int)Hh << 8) | Hl;
166+
w = ((int)Wh << 8) | Wl;
167+
f.close();
168+
return (w > 0 && h > 0);
169+
} else {
170+
uint32_t toSkip = (uint32_t)segLen - 2;
171+
if (!skipN(toSkip)) { f.close(); return false; }
172+
}
173+
}
174+
175+
f.close();
176+
return false;
177+
}
178+
179+
static bool getImageWH_SdFat(const char* path, int &w, int &h) {
180+
const char *ext = strrchr(path, '.');
181+
if (ext) {
182+
char e1 = tolower(*(ext+1));
183+
char e2 = tolower(*(ext+2));
184+
char e3 = tolower(*(ext+3));
185+
if ((e1=='b' && e2=='m' && e3=='p')) {
186+
if (readBmpSize_SdFat(path, w, h)) return true;
187+
} else if ((e1=='j' && e2=='p' && e3=='g') ||
188+
(e1=='j' && e2=='p' && e3=='e')) {
189+
if (readJpegSize_SdFat(path, w, h)) return true;
190+
}
191+
}
192+
w = 800; h = 600;
193+
return false;
194+
}
195+
196+
void showImage(const char* path) {
197+
Serial.printf("Displaying: %s\n", path);
198+
display.clearDisplay();
199+
display.sdCardInit();
200+
201+
int imgW = 0, imgH = 0;
202+
bool okSize = getImageWH_SdFat(path, imgW, imgH);
203+
Serial.printf("Image size detected: %dx%d (ok=%s)\n", imgW, imgH, okSize ? "yes" : "no");
204+
205+
const int dispW = display.width();
206+
const int dispH = display.height();
207+
208+
int drawW = imgW;
209+
int drawH = imgH;
210+
211+
int x = (dispW - drawW) / 2;
212+
int y = (dispH - drawH) / 2;
213+
214+
Serial.printf("Draw at x=%d, y=%d (disp=%dx%d)\n", x, y, dispW, dispH);
215+
216+
if (!display.drawImage(path, x, y, 3)) {
217+
display.setTextSize(2);
218+
display.setCursor(100, 300);
219+
display.println("Image load failed!");
220+
}
221+
222+
// Overlay text (white on black background)
223+
const char* overlayText = "Inkplate LAN Gallery on langallery.local";
224+
display.setTextSize(1);
225+
226+
int16_t textW = strlen(overlayText) * 6;
227+
int16_t textH = 10;
228+
int16_t padding = 6;
229+
230+
int16_t boxX = display.width() - textW - padding * 2;
231+
int16_t boxY = display.height() - textH - padding * 2;
232+
int16_t boxW = textW + padding * 2;
233+
int16_t boxH = textH + padding * 2;
234+
235+
// Black rectangle behind text
236+
display.fillRect(boxX, boxY, boxW, boxH, 0);
237+
238+
// White text on top
239+
display.setTextColor(7);
240+
display.setCursor(boxX + padding, boxY + textH - 2);
241+
display.print(overlayText);
242+
243+
display.display();
244+
}
245+
246+
void startFileUpload(const char* filename) {
247+
if (xSemaphoreTake(sdMutex, portMAX_DELAY)) {
248+
display.sdCardInit();
249+
if (currentUploadFile) {
250+
currentUploadFile->close();
251+
delete currentUploadFile;
252+
}
253+
currentUploadFile = new SdFile();
254+
bool opened = currentUploadFile->open(filename, O_WRITE | O_CREAT | O_TRUNC);
255+
Serial.printf("Opening %s: %s\n", filename, opened ? "SUCCESS" : "FAILED");
256+
xSemaphoreGive(sdMutex);
257+
}
258+
}
259+
260+
void writeFileData(uint8_t* data, size_t len) {
261+
if (xSemaphoreTake(sdMutex, portMAX_DELAY)) {
262+
if (currentUploadFile && currentUploadFile->isOpen()) {
263+
size_t written = currentUploadFile->write(data, len);
264+
Serial.printf("Wrote %d/%d bytes\n", written, len);
265+
} else {
266+
Serial.println("ERROR: File not open!");
267+
}
268+
xSemaphoreGive(sdMutex);
269+
}
270+
}
271+
272+
void finishFileUpload() {
273+
if (xSemaphoreTake(sdMutex, portMAX_DELAY)) {
274+
if (currentUploadFile) {
275+
if (currentUploadFile->isOpen()) {
276+
currentUploadFile->sync();
277+
currentUploadFile->close();
278+
Serial.println("File closed and synced");
279+
}
280+
delete currentUploadFile;
281+
currentUploadFile = nullptr;
282+
}
283+
uploadComplete = true;
284+
xSemaphoreGive(sdMutex);
285+
}
286+
}
287+
288+
void setup() {
289+
Serial.begin(115200);
290+
display.begin();
291+
randomSeed(analogRead(0));
292+
sdMutex = xSemaphoreCreateMutex();
293+
294+
display.clearDisplay();
295+
display.setTextSize(3);
296+
display.setCursor(0, 0);
297+
display.println("Connecting Wi-Fi...");
298+
display.display();
299+
300+
WiFi.begin(ssid, password);
301+
while (WiFi.status() != WL_CONNECTED) {
302+
delay(500);
303+
Serial.print(".");
304+
}
305+
Serial.printf("\nConnected! IP: %s\n", WiFi.localIP().toString().c_str());
306+
307+
setupWebServer();
308+
309+
if (buildImageList()) {
310+
Node* n = pickRandomNode();
311+
if (n) showImage(n->path);
312+
} else {
313+
display.clearDisplay();
314+
display.setTextSize(3);
315+
display.setCursor(100, 300);
316+
display.println("No images found on SD!");
317+
display.display();
318+
}
319+
}
320+
321+
unsigned long lastImageChange = 0;
322+
323+
void loop() {
324+
if (uploadComplete) {
325+
uploadComplete = false;
326+
Serial.println("Upload complete, rebuilding list...");
327+
delay(100);
328+
if (xSemaphoreTake(sdMutex, portMAX_DELAY)) {
329+
if (buildImageList()) {
330+
Serial.printf("Found %d images\n", nodeCount);
331+
Node* n = pickRandomNode();
332+
if (n) {
333+
Serial.printf("Picked image: %s\n", n->path);
334+
showImage(n->path);
335+
}
336+
}
337+
xSemaphoreGive(sdMutex);
338+
}
339+
lastImageChange = millis();
340+
}
341+
342+
if (millis() - lastImageChange >= IMAGE_CHANGE_INTERVAL) {
343+
if (xSemaphoreTake(sdMutex, portMAX_DELAY)) {
344+
if (buildImageList()) {
345+
Node* n = pickRandomNode();
346+
if (n) showImage(n->path);
347+
}
348+
xSemaphoreGive(sdMutex);
349+
}
350+
lastImageChange = millis();
351+
}
352+
353+
delay(10);
354+
}

0 commit comments

Comments
 (0)