--- /dev/null
+#include "include/bmp.h"
+#include "include/sd_card.h"
+
+BMP::BMP(const char* filename) :
+ bmp_file{SD_card_fs::get_file(filename)},
+ data_idx{0}
+{
+
+ bmp_file->seek(10); // idx of 4 bytes representing start of image data
+
+ data_idx = bmp_file->read();
+
+ for(uint8_t i{0}; i < 3; i++)
+ {
+ data_idx << 8;
+ data_idx |= bmp_file->read();
+ }
+
+ bmp_file->seek(data_idx);
+}
+
+char BMP::read_pixel()
+{
+ return bmp_file->read();
+}
+
+void BMP::read_pixel(const char* buffer, const uint32_t len)
+{
+ bmp_file->read((uint8_t*)buffer, len);
+}
+
+uint32_t BMP::position() const
+{
+ return bmp_file->position();
+}
+
+uint32_t BMP::available() const
+{
+ return bmp_file->available();
+}
+
+void BMP::seek(const uint32_t pos)
+{
+ if (pos < data_idx) bmp_file->seek(data_idx);
+ else bmp_file->seek(pos);
+}
--- /dev/null
+#include "include/sd_card.h"
+#include "include/bmp.h"
+#include "include/web_server.h"
+
+/**
+ * @file e-paper-project.ino
+ * @author Nils Forssén (nilsforssen.se)
+ * @brief
+ * Project-sketch to fetch images from an web-server, process them (scaling and dithering)
+ * and then display them on an E-paper-display (SPI). Should switch image every 24 hours
+ * and go to sleep in between. Should support adding, removing and listing images on SD-card
+ * via a webserver as long as a physical switch is set high. When switched low, the
+ * device go back to the daily sleep->image->sleep cycle.
+ *
+ * @version 1.0
+ * @date 2023-12-13
+ *
+ * @copyright Copyright (c) 2023
+ */
+
+
+/**
+ * Connect the SPI bus to SD-card and E-paper
+ * MOSI - GPIO 23
+ * MISO - GPIO 19
+ * SCLK - GPIO 18
+ * CS - [SD to GPIO 5] or [E-paper to GPIO 17]
+*/
+
+void setup(){
+ Serial.begin(115200);
+
+ SD_card_fs::init_SD(5);
+ Web_server::init_wifi();
+ Web_server::init_server();
+
+}
+
+void loop()
+{
+ Web_server::handle_client();
+}
\ No newline at end of file
--- /dev/null
+#include "include/epd_spi.h"
+#include "include/bmp.h"
+
+EPD_SPI_interface::EPD_SPI_interface(const epd_spi_pinout& p) : pinout{p}
+{
+ // init SPI pins
+ pinMode(pinout.pin_busy, INPUT);
+ pinMode(pinout.pin_rst, OUTPUT);
+ pinMode(pinout.pin_dc, OUTPUT);
+ pinMode(pinout.pin_clk, OUTPUT);
+ pinMode(pinout.pin_din, OUTPUT);
+ pinMode(pinout.pin_cs, OUTPUT);
+
+ digitalWrite(pinout.pin_cs , HIGH);
+ digitalWrite(pinout.pin_clk, LOW);
+
+ // init EPD
+ digitalWrite(pinout.pin_rst, HIGH);
+ delay(200);
+ digitalWrite(pinout.pin_rst, LOW);
+ delay(5);
+ digitalWrite(pinout.pin_rst, HIGH);
+ delay(200);
+
+ // send init-data
+ waitfor_busyhigh();
+ send_command(0x00);
+ send_byte(0xEF);
+ send_byte(0x08);
+ send_command(0x01);
+ send_byte(0x37);
+ send_byte(0x00);
+ send_byte(0x23);
+ send_byte(0x23);
+ send_command(0x03);
+ send_byte(0x00);
+ send_command(0x06);
+ send_byte(0xC7);
+ send_byte(0xC7);
+ send_byte(0x1D);
+ send_command(0x30);
+ send_byte(0x3C);
+ send_command(0x41);
+ send_byte(0x00);
+ send_command(0x50);
+ send_byte(0x37);
+ send_command(0x60);
+ send_byte(0x22);
+ send_command(0x61);
+ send_byte(0x02);
+ send_byte(0x58);
+ send_byte(0x01);
+ send_byte(0xC0);
+ send_command(0xE3);
+ send_byte(0xAA);
+ delay(100);
+ send_command(0x50);
+ send_byte(0x37);
+ send_command(0x61);
+ send_byte(0x02);
+ send_byte(0x58);
+ send_byte(0x01);
+ send_byte(0xC0);
+ send_command(0x10);
+}
+
+void EPD_SPI_interface::send_byte(byte data)
+{
+ digitalWrite(pinout.pin_dc, HIGH);
+ send_spi_data(data);
+}
+
+void EPD_SPI_interface::send_command(byte command)
+{
+ digitalWrite(pinout.pin_dc, LOW);
+ send_spi_data(command);
+}
+
+void EPD_SPI_interface::show()
+{
+ send_command(0x04);
+ waitfor_busyhigh();
+ send_command(0x12);
+ waitfor_busyhigh();
+ send_command(0x02);
+ waitfor_busylow();
+ delay(200);
+}
+
+void EPD_SPI_interface::sleep()
+{
+ delay(100);
+ send_command(0x07);
+ send_byte(0xA5);
+ delay(100);
+ digitalWrite(pinout.pin_rst, LOW);
+}
+
+void EPD_SPI_interface::send_spi_data(byte data)
+{
+ digitalWrite(pinout.pin_cs, LOW);
+
+ for (int i = 0; i < 8; i++)
+ {
+ if ((data & 0x80))
+ {
+ digitalWrite(pinout.pin_din, HIGH);
+ }
+ else
+ {
+ digitalWrite(pinout.pin_din, LOW);
+ }
+
+ data <<= 1;
+ digitalWrite(pinout.pin_clk, HIGH);
+ digitalWrite(pinout.pin_clk, LOW);
+ }
+
+ digitalWrite(pinout.pin_cs, HIGH);
+}
+
+void EPD_SPI_interface::waitfor_busyhigh() const
+{
+ while(!(digitalRead(pinout.pin_busy)));
+}
+
+void EPD_SPI_interface::waitfor_busylow() const
+{
+ while(digitalRead(pinout.pin_busy));
+}
+
+void EPD_SPI_interface::display(std::unique_ptr<BMP> bmp)
+{
+ bmp->seek(0);
+ for (uint32_t i{0}; i < EPD_HEIGHT; i++)
+ {
+ for (uint32_t j{0}; j < EPD_WIDTH / 2; i++) // Every byte is 2 pixels
+ {
+ send_byte(bmp->read_pixel());
+ }
+ }
+ show();
+}
+
+void EPD_SPI_interface::fill(color c)
+{
+ for (uint32_t i{0}; i < EPD_HEIGHT; i++)
+ {
+ for (uint32_t j{0}; j < EPD_WIDTH / 2; i++) // Every byte is 2 pixels
+ {
+ send_byte((c << 4)|c);
+ }
+ }
+ show();
+}
\ No newline at end of file
--- /dev/null
+#ifndef BMP_H_
+#define BMP_H_
+
+/**
+ * @file sd_card.h
+ * @author Nils Forssén (nilsforssen.se)
+ * @brief
+ * Useful filesystem functions for interacting with an SD-card
+ *
+ * @version 0.1
+ * @date 2023-12-13
+ *
+ * @copyright Copyright (c) 2023
+ */
+
+#include <SD.h>
+#include <string>
+
+class BMP
+{
+public:
+ BMP(const char* filename);
+ ~BMP() = default; // Smart pointer closes itself!
+
+ char read_pixel();
+ void read_pixel(const char* buffer, const uint32_t len);
+ uint32_t position() const;
+ uint32_t available() const;
+ void seek(const uint32_t pos);
+
+protected:
+private:
+ std::unique_ptr<File> bmp_file;
+ uint32_t data_idx;
+};
+
+
+#endif
\ No newline at end of file
--- /dev/null
+#ifndef SIMPLE_SPI_H_
+#define SIMPLE_SPI_H_
+
+#include <Arduino.h>
+#include <memory>
+
+#define EPD_WIDTH 600
+#define EPD_HEIGHT 448
+
+enum color: byte
+{
+ EPD_5IN65F_BLACK, /// 000
+ EPD_5IN65F_WHITE, /// 001
+ EPD_5IN65F_GREEN, /// 010
+ EPD_5IN65F_BLUE, /// 011
+ EPD_5IN65F_RED, /// 100
+ EPD_5IN65F_YELLOW, /// 101
+ EPD_5IN65F_ORANGE /// 110
+};
+
+struct epd_spi_pinout
+{
+ const uint8_t pin_busy;
+ const uint8_t pin_rst;
+ const uint8_t pin_dc;
+ const uint8_t pin_cs;
+ const uint8_t pin_clk;
+ const uint8_t pin_din;
+};
+
+class BMP;
+
+class EPD_SPI_interface
+{
+public:
+ EPD_SPI_interface(const epd_spi_pinout& p);
+ ~EPD_SPI_interface() = default;
+
+ void send_byte(byte data);
+ void send_command(byte command);
+ void display(std::unique_ptr<BMP> bmp);
+ void fill(color c);
+
+ void show();
+ void sleep();
+protected:
+private:
+ void send_spi_data(byte data);
+ void waitfor_busyhigh() const;
+ void waitfor_busylow() const;
+
+ const epd_spi_pinout pinout;
+};
+
+#endif
--- /dev/null
+#ifndef SD_CARD_H_
+#define SD_CARD_H_
+
+/**
+ * @file sd_card.h
+ * @author Nils Forssén (nilsforssen.se)
+ * @brief
+ * Useful filesystem functions for interacting with an SD-card
+ *
+ * @date 2023-12-13
+ *
+ * @copyright Copyright (c) 2023
+ */
+
+#include <memory>
+#include <SD.h>
+
+typedef enum : uint8_t
+{
+ FS_OK = 0,
+ FS_FAIL = 1,
+ FS_WARNING = 2,
+} fs_error_t;
+
+namespace SD_card_fs
+{
+ fs_error_t init_SD(const uint16_t cs);
+ fs_error_t create_dir(const char* path);
+ fs_error_t remove_dir(const char* path);
+ std::unique_ptr<File> get_file(const char* path);
+ fs_error_t write_file(const char* path, const char* data); // Make sure data is null-terminated
+ fs_error_t append_file(const char* path, const char* data); // Make sure data is null-terminated
+ fs_error_t rename_file(const char* path1, const char* path2);
+ fs_error_t delete_file(const char* path);
+};
+
+
+#endif
\ No newline at end of file
--- /dev/null
+#ifndef WEB_HTML_H_
+#define WEB_HTML_H_
+
+/**
+ * @file web_HTML.h
+ * @author Nils Forssén (nilsforssen.se)
+ * @brief
+ * web-server functionality based on https://github.com/G6EJD/ESP32-ESP8266-File-Download-Upload-Delete-Stream-and-Directory/tree/master
+ *
+ * @date 2023-12-14
+ *
+ * @copyright Copyright (c) 2023
+ */
+
+namespace HTML_data
+{
+ const char home_page_str[] PROGMEM = "<a href='/download'><button>Download</button></a><a href='/upload'><button>Upload</button></a><a href='/stream'><button>Stream</button></a><a href='/delete'><button>Delete</button></a><a href='/dir'><button>Directory</button></a>";
+
+ const char file_upload_str[] PROGMEM = "<h3>Select File to Upload</h3><FORM action='/fupload' method='post' enctype='multipart/form-data'><input class='buttons' style='width:40%' type='file' name='fupload' id = 'fupload' value=''><br><br><button class='buttons' style='width:10%' type='submit'>Upload File</button><br><a href='/'>[Back]</a><br><br>";
+
+ const char upload_finished_str[] PROGMEM = "<h3>File was successfully uploaded</h3>";
+
+ const char append_page_header[] PROGMEM = "<!DOCTYPE html><html><head><title>File Server</title><meta name='viewport' content='user-scalable=yes,initial-scale=1.0,width=device-width'><style>body{max-width:65%;margin:0 auto;font-family:arial;font-size:105%;text-align:center;color:blue;background-color:#F7F2Fd;}ul{list-style-type:none;margin:0.1em;padding:0;border-radius:0.375em;overflow:hidden;background-color:#dcade6;font-size:1em;}li{float:left;border-radius:0.375em;border-right:0.06em solid #bbb;}last-child {border-right:none;font-size:85%}li a{display: block;border-radius:0.375em;padding:0.44em 0.44em;text-decoration:none;font-size:85%}li a:hover{background-color:#EAE3EA;border-radius:0.375em;font-size:85%}section {font-size:0.88em;}h1{color:white;border-radius:0.5em;font-size:1em;padding:0.2em 0.2em;background:#558ED5;}h2{color:orange;font-size:1.0em;}h3{font-size:0.8em;}table{font-family:arial,sans-serif;font-size:0.9em;border-collapse:collapse;width:85%;} th,td {border:0.06em solid #dddddd;text-align:left;padding:0.3em;border-bottom:0.06em solid #dddddd;} tr:nth-child(odd) {background-color:#eeeeee;}.rcorners_n {border-radius:0.5em;background:#558ED5;padding:0.3em 0.3em;width:20%;color:white;font-size:75%;}.rcorners_m {border-radius:0.5em;background:#558ED5;padding:0.3em 0.3em;width:50%;color:white;font-size:75%;}.rcorners_w {border-radius:0.5em;background:#558ED5;padding:0.3em 0.3em;width:70%;color:white;font-size:75%;}.column{float:left;width:50%;height:45%;}.row:after{content:'';display:table;clear:both;}*{box-sizing:border-box;}footer{background-color:#eedfff; text-align:center;padding:0.3em 0.3em;border-radius:0.375em;font-size:60%;}button{border-radius:0.5em;background:#558ED5;padding:0.3em 0.3em;width:20%;color:white;font-size:130%;}.buttons {border-radius:0.5em;background:#558ED5;padding:0.3em 0.3em;width:15%;color:white;font-size:80%;}.buttonsm{border-radius:0.5em;background:#558ED5;padding:0.3em 0.3em;width:9%; color:white;font-size:70%;}.buttonm {border-radius:0.5em;background:#558ED5;padding:0.3em 0.3em;width:15%;color:white;font-size:70%;}.buttonw {border-radius:0.5em;background:#558ED5;padding:0.3em 0.3em;width:40%;color:white;font-size:70%;}a{font-size:75%;}p{font-size:75%;}</style></head><body><h1>File Server</h1>";
+
+ const char append_page_footer[] PROGMEM = "<ul><li><a href='/'>Home</a></li><li><a href='/download'>Download</a></li> <li><a href='/upload'>Upload</a></li> <li><a href='/stream'>Stream</a></li> <li><a href='/delete'>Delete</a></li> <li><a href='/dir'>Directory</a></li></ul></body></html>";
+};
+
+
+#endif
\ No newline at end of file
--- /dev/null
+#ifndef WEB_SERVER_H_
+#define WEB_SERVER_H_
+
+/**
+ * @file web_server.h
+ * @author Nils Forssén (nilsforssen.se)
+ * @brief
+ * web-server functionality based on https://github.com/G6EJD/ESP32-ESP8266-File-Download-Upload-Delete-Stream-and-Directory/tree/master
+ *
+ * @date 2023-12-14
+ *
+ * @copyright Copyright (c) 2023
+ */
+
+#ifdef ESP8266
+ #include <ESP8266WebServer.h>
+ #include <ESP8266WiFi.h>
+ #include <ESP8266WiFiMulti.h>
+#else
+ #include <ESP32WebServer.h>
+ #include <WiFi.h>
+#endif
+
+#include "credentials.h"
+
+#define SERVER_PORT 80
+
+namespace Web_server
+{
+ void init_wifi();
+ void init_server();
+ void handle_client();
+};
+
+#endif
\ No newline at end of file
--- /dev/null
+#include "include/sd_card.h"
+
+namespace SD_card_fs
+{
+ fs_error_t init_SD(const uint16_t cs)
+ {
+ if(!SD.begin(cs)){
+ Serial.println("Card Mount Failed");
+ return FS_FAIL;
+ }
+ uint8_t cardType = SD.cardType();
+
+ if(cardType == CARD_NONE){
+ Serial.println("No SD card attached");
+ return FS_FAIL;
+ }
+
+ Serial.printf("SD card initialized!\nSize: %lluMB\n", SD.cardSize() / (1024 * 1024));
+ return FS_OK;
+ }
+
+ fs_error_t create_dir(const char* path)
+ {
+ if(SD.mkdir(path)){
+ Serial.printf("Created directory: %s\n", path);
+ return FS_OK;
+ } else {
+ Serial.printf("failed to create directory: %s\n", path);
+ return FS_FAIL;
+ }
+ }
+
+ fs_error_t remove_dir(const char* path)
+ {
+ if(SD.rmdir(path)){
+ Serial.printf("Removed directory: %s\n", path);
+ return FS_OK;
+ } else {
+ Serial.printf("failed to remove directory: %s\n", path);
+ return FS_FAIL;
+ }
+ }
+
+ std::unique_ptr<File> get_file(const char* path)
+ {
+ File* file = new File{SD.open(path)};
+ if(!(*file)){
+ Serial.printf("failed to open file: %s\n", path);
+ return nullptr;
+ }
+ return std::unique_ptr<File>{file};
+ }
+
+ fs_error_t write_file(const char* path, const char* data)
+ {
+ File file = SD.open(path, FILE_WRITE);
+ if(!file){
+ Serial.printf("failed to open file: %s\n", path);
+ return FS_FAIL;
+ }
+
+ if(file.print(data)){
+ Serial.printf("File written: %s\n", path);
+ file.close();
+ return FS_OK;
+ } else {
+ Serial.printf("Write failed: %s\n", path);
+ file.close();
+ return FS_FAIL;
+ }
+ }
+
+ fs_error_t append_file(const char* path, const char* data){
+ File file = SD.open(path, FILE_APPEND);
+ if(!file){
+ Serial.printf("failed to open file for appending: %s\n", path);
+ return FS_FAIL;
+ }
+
+ if(file.print(data)){
+ Serial.printf("Message appended: %s\n", path);
+ file.close();
+ return FS_OK;
+ } else {
+ Serial.printf("Append failed: %s\n", path);
+ file.close();
+ return FS_FAIL;
+ }
+ }
+
+ fs_error_t rename_file(const char* path1, const char* path2){
+ if (SD.rename(path1, path2)) {
+ Serial.printf("File renamed: %s to %s\n", path1, path2);
+ return FS_OK;
+ } else {
+ Serial.printf("Rename failed: %s to %s\n", path1, path2);
+ return FS_FAIL;
+ }
+ }
+
+ fs_error_t delete_file(const char* path){
+ if(SD.remove(path)){
+ Serial.printf("File deleted: %s\n", path);
+ return FS_OK;
+ } else {
+ Serial.printf("Delete failed: %s\n", path);
+ return FS_FAIL;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+#include "include/web_server.h"
+#include "include/web_HTML.h"
+
+#ifdef ESP8266
+ static ESP8266WebServer server{SERVER_PORT};
+#else
+ static ESP32WebServer server{SERVER_PORT};
+#endif
+
+static void send_HTML_page(const char* webpage)
+{
+ server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+ server.sendHeader("Pragma", "no-cache");
+ server.sendHeader("Expires", "-1");
+ server.setContentLength(CONTENT_LENGTH_UNKNOWN);
+ server.send(200, "text/html", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
+ server.sendContent(HTML_data::append_page_header);
+
+ server.sendContent(webpage);
+ server.sendContent("");
+ server.client().stop();
+}
+
+static void home_page()
+{
+ send_HTML_page(HTML_data::home_page_str);
+}
+
+static void file_upload()
+{
+ send_HTML_page(HTML_data::file_upload_str);
+}
+
+static void handle_file_upload()
+{
+ HTTPUpload &uploadfile = server.upload();
+
+ if (uploadfile.status == UPLOAD_FILE_START)
+ {
+ String filename {uploadfile.filename};
+ if (!filename.startsWith("/"))
+ filename = "/" + filename;
+
+ Serial.print("Upload File Name: ");
+ Serial.println(filename);
+ // SD.remove(filename); // Remove a previous version, otherwise data is appended the file again
+ // UploadFile = SD.open(filename, FILE_WRITE); // Open the file for writing in SPIFFS (create it, if doesn't exist)
+ }
+ else if (uploadfile.status == UPLOAD_FILE_WRITE)
+ {
+ Serial.println("Writing file somewhere");
+ }
+ else if (uploadfile.status == UPLOAD_FILE_END)
+ {
+ Serial.print("Upload Size: ");
+ Serial.println(uploadfile.totalSize);
+ }
+}
+
+namespace Web_server
+{
+ void init_server()
+ {
+ server.on("/", home_page);
+ server.on("/upload", file_upload);
+ server.on("/fupload", HTTP_POST,[](){server.send(200);}, handle_file_upload);
+ //server.on("/delete", file_delete);
+ //server.on("/dir", SD_dir);
+ Serial.println("Web-server starting!");
+ server.begin();
+ }
+
+ void init_wifi()
+ {
+ WiFi.mode(WIFI_STA);
+ WiFi.begin(ssid, passw);
+ Serial.println("Connecting");
+
+ while(WiFi.status() != WL_CONNECTED){
+ Serial.print(".");
+ delay(500);
+ }
+
+ Serial.println("\nConnected to the WiFi network");
+ Serial.print("Local ESP32 IP: ");
+ Serial.println(WiFi.localIP());
+ }
+
+ void handle_client()
+ {
+ server.handleClient();
+ }
+};
+
+
+
+
+
+/*
+void Web_server::SD_dir()
+{
+ if (SD_present)
+ {
+ File root = SD.open("/");
+ if (root)
+ {
+ root.rewindDirectory();
+ SendHTML_Header();
+ webpage += F("<h3 class='rcorners_m'>SD Card Contents</h3><br>");
+ webpage += F("<table align='center'>");
+ webpage += F("<tr><th>Name/Type</th><th style='width:20%'>Type File/Dir</th><th>File Size</th></tr>");
+ printDirectory("/", 0);
+ webpage += F("</table>");
+ SendHTML_Content();
+ root.close();
+ }
+ else
+ {
+ SendHTML_Header();
+ webpage += F("<h3>No Files Found</h3>");
+ }
+ append_page_footer();
+ SendHTML_Content();
+ SendHTML_Stop(); // Stop is needed because no content length was sent
+ }
+ else
+ ReportSDNotPresent();
+}
+
+ void printDirectory(const char *dirname, uint8_t levels)
+ {
+ File root = SD.open(dirname);
+ #ifdef ESP8266
+ root.rewindDirectory(); // Only needed for ESP8266
+ #endif
+ if (!root)
+ {
+ return;
+ }
+ if (!root.isDirectory())
+ {
+ return;
+ }
+ File file = root.openNextFile();
+ while (file)
+ {
+ if (webpage.length() > 1000)
+ {
+ SendHTML_Content();
+ }
+ if (file.isDirectory())
+ {
+ Serial.println(String(file.isDirectory() ? "Dir " : "File ") + String(file.name()));
+ webpage += "<tr><td>" + String(file.isDirectory() ? "Dir" : "File") + "</td><td>" + String(file.name()) + "</td><td></td></tr>";
+ printDirectory(file.name(), levels - 1);
+ }
+ else
+ {
+ // Serial.print(String(file.name())+"\t");
+ webpage += "<tr><td>" + String(file.name()) + "</td>";
+ Serial.print(String(file.isDirectory() ? "Dir " : "File ") + String(file.name()) + "\t");
+ webpage += "<td>" + String(file.isDirectory() ? "Dir" : "File") + "</td>";
+ int bytes = file.size();
+ String fsize = "";
+ if (bytes < 1024)
+ fsize = String(bytes) + " B";
+ else if (bytes < (1024 * 1024))
+ fsize = String(bytes / 1024.0, 3) + " KB";
+ else if (bytes < (1024 * 1024 * 1024))
+ fsize = String(bytes / 1024.0 / 1024.0, 3) + " MB";
+ else
+ fsize = String(bytes / 1024.0 / 1024.0 / 1024.0, 3) + " GB";
+ webpage += "<td>" + fsize + "</td></tr>";
+ Serial.println(String(fsize));
+ }
+ file = root.openNextFile();
+ }
+ file.close();
+ }
+
+ void SD_file_stream(String filename)
+ {
+ if (SD_present)
+ {
+ File dataFile = SD.open("/" + filename, FILE_READ); // Now read data from SD Card
+ Serial.print("Streaming file: ");
+ Serial.println(filename);
+ if (dataFile)
+ {
+ if (dataFile.available())
+ { // If data is available and present
+ String dataType = "application/octet-stream";
+ if (server.streamFile(dataFile, dataType) != dataFile.size())
+ {
+ Serial.print(F("Sent less data than expected!"));
+ }
+ }
+ dataFile.close(); // close the file:
+ }
+ else
+ ReportFileNotPresent("Cstream");
+ }
+ else
+ ReportSDNotPresent();
+ }
+
+ void File_Delete()
+ {
+ if (server.args() > 0)
+ { // Arguments were received
+ if (server.hasArg("delete"))
+ SD_file_delete(server.arg(0));
+ }
+ else
+ SelectInput("Select a File to Delete", "delete", "delete");
+ }
+
+ void SD_file_delete(String filename)
+ { // Delete the file
+ if (SD_present)
+ {
+ SendHTML_Header();
+ File dataFile = SD.open("/" + filename, FILE_READ); // Now read data from SD Card
+ Serial.print("Deleting file: ");
+ Serial.println(filename);
+ if (dataFile)
+ {
+ if (SD.remove("/" + filename))
+ {
+ Serial.println(F("File deleted successfully"));
+ webpage += "<h3>File '" + filename + "' has been erased</h3>";
+ webpage += F("<a href='/delete'>[Back]</a><br><br>");
+ }
+ else
+ {
+ webpage += F("<h3>File was not deleted - error</h3>");
+ webpage += F("<a href='delete'>[Back]</a><br><br>");
+ }
+ }
+ else
+ ReportFileNotPresent("delete");
+ append_page_footer();
+ SendHTML_Content();
+ SendHTML_Stop();
+ }
+ else
+ ReportSDNotPresent();
+ }
+
+ void SendHTML_Header()
+ {
+ server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+ server.sendHeader("Pragma", "no-cache");
+ server.sendHeader("Expires", "-1");
+ server.setContentLength(CONTENT_LENGTH_UNKNOWN);
+ server.send(200, "text/html", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
+ append_page_header();
+ server.sendContent(webpage);
+ webpage = "";
+ }
+
+
+ void ReportSDNotPresent()
+ {
+ SendHTML_Header();
+ webpage += F("<h3>No SD Card present</h3>");
+ webpage += F("<a href='/'>[Back]</a><br><br>");
+ append_page_footer();
+ SendHTML_Content();
+ SendHTML_Stop();
+ }
+
+ void ReportFileNotPresent(String target)
+ {
+ SendHTML_Header();
+ webpage += F("<h3>File does not exist</h3>");
+ webpage += F("<a href='/");
+ webpage += target + "'>[Back]</a><br><br>";
+ append_page_footer();
+ SendHTML_Content();
+ SendHTML_Stop();
+ }
+
+ void ReportCouldNotCreateFile(String target)
+ {
+ SendHTML_Header();
+ webpage += F("<h3>Could Not Create Uploaded File (write-protected?)</h3>");
+ webpage += F("<a href='/");
+ webpage += target + "'>[Back]</a><br><br>";
+ append_page_footer();
+ SendHTML_Content();
+ SendHTML_Stop();
+ }
+}
+*/
\ No newline at end of file
+++ /dev/null
-#include "include/bmp.h"
-#include "include/sd_card.h"
-
-BMP::BMP(const char* filename) :
- bmp_file{SD_card_fs::get_file(filename)},
- data_idx{0}
-{
-
- bmp_file->seek(10); // idx of 4 bytes representing start of image data
-
- data_idx = bmp_file->read();
-
- for(uint8_t i{0}; i < 3; i++)
- {
- data_idx << 8;
- data_idx |= bmp_file->read();
- }
-
- bmp_file->seek(data_idx);
-}
-
-byte BMP::read_pixel()
-{
- return bmp_file->read();
-}
-
-void BMP::read_pixel(const char* buffer, const uint32_t len)
-{
- bmp_file->read(buffer, len);
-}
-
-uint32_t BMP::position() const
-{
- return bmp_file->position();
-}
-
-uint32_t BMP::available() const
-{
- return bmp_file->available();
-}
-
-void BMP::seek(const uint32_t pos)
-{
- if (pos < data_idx) bmp_file->seek(data_idx);
- else bmp_file->seek(pos);
-}
+++ /dev/null
-#include "include/sd_card.h"
-#include "include/bmp.h"
-#include "Arduino.h"
-
-/**
- * @file e-paper-project.ino
- * @author Nils Forssén (nilsforssen.se)
- * @brief
- * Project-sketch to fetch images from an web-server, process them (scaling and dithering)
- * and then display them on an E-paper-display (SPI). Should switch image every 24 hours
- * and go to sleep in between. Should support adding, removing and listing images on SD-card
- * via a webserver as long as a physical switch is set high. When switched low, the
- * device go back to the daily sleep->image->sleep cycle.
- *
- * @version 0.1
- * @date 2023-12-13
- *
- * @copyright Copyright (c) 2023
- */
-
-
-/**
- * Connect the SPI bus to SD-card and E-paper
- * MOSI - GPIO 23
- * MISO - GPIO 19
- * SCLK - GPIO 18
- * CS - [SD to GPIO 5] or [E-paper to GPIO 17]
-*/
-
-
-void setup(){
- Serial.begin(115200);
-
- if(!SD.begin(5)){
- Serial.println("Card Mount Failed");
- return;
- }
- uint8_t cardType = SD.cardType();
-
- if(cardType == CARD_NONE){
- Serial.println("No SD card attached");
- return;
- }
-
- Serial.printf("SD card initialized!\nSize: %lluMB\n", SD.cardSize() / (1024 * 1024));
-}
-
-void loop()
-{
- BMP bmp_file{"/download.bmp"};
- while (true)
- {
- Serial.printf("Read pixel of txt file: %x\n", bmp_file.read_pixel() & 0xff);
- delay(1000);
- }
-
-}
\ No newline at end of file
+++ /dev/null
-#include "include/epd_spi.h"
-#include "include/bmp.h"
-
-EPD_SPI_interface::EPD_SPI_interface(const epd_spi_pinout& p) : pinout{p}
-{
- // init SPI pins
- pinMode(pinout.pin_busy, INPUT);
- pinMode(pinout.pin_rst, OUTPUT);
- pinMode(pinout.pin_dc, OUTPUT);
- pinMode(pinout.pin_clk, OUTPUT);
- pinMode(pinout.pin_din, OUTPUT);
- pinMode(pinout.pin_cs, OUTPUT);
-
- digitalWrite(pinout.pin_cs , HIGH);
- digitalWrite(pinout.pin_clk, LOW);
-
- // init EPD
- digitalWrite(pinout.pin_rst, HIGH);
- delay(200);
- digitalWrite(pinout.pin_rst, LOW);
- delay(5);
- digitalWrite(pinout.pin_rst, HIGH);
- delay(200);
-
- // send init-data
- waitfor_busyhigh();
- send_command(0x00);
- send_byte(0xEF);
- send_byte(0x08);
- send_command(0x01);
- send_byte(0x37);
- send_byte(0x00);
- send_byte(0x23);
- send_byte(0x23);
- send_command(0x03);
- send_byte(0x00);
- send_command(0x06);
- send_byte(0xC7);
- send_byte(0xC7);
- send_byte(0x1D);
- send_command(0x30);
- send_byte(0x3C);
- send_command(0x41);
- send_byte(0x00);
- send_command(0x50);
- send_byte(0x37);
- send_command(0x60);
- send_byte(0x22);
- send_command(0x61);
- send_byte(0x02);
- send_byte(0x58);
- send_byte(0x01);
- send_byte(0xC0);
- send_command(0xE3);
- send_byte(0xAA);
- delay(100);
- send_command(0x50);
- send_byte(0x37);
- send_command(0x61);
- send_byte(0x02);
- send_byte(0x58);
- send_byte(0x01);
- send_byte(0xC0);
- send_command(0x10);
-}
-
-void EPD_SPI_interface::send_byte(byte data)
-{
- digitalWrite(pinout.pin_dc, HIGH);
- send_spi_data(data);
-}
-
-void EPD_SPI_interface::send_command(byte command)
-{
- digitalWrite(pinout.pin_dc, LOW);
- send_spi_data(command);
-}
-
-void EPD_SPI_interface::show()
-{
- send_command(0x04);
- waitfor_busyhigh();
- send_command(0x12);
- waitfor_busyhigh();
- send_command(0x02);
- waitfor_busylow();
- delay(200);
-}
-
-void EPD_SPI_interface::sleep()
-{
- delay(100);
- send_command(0x07);
- send_byte(0xA5);
- delay(100);
- digitalWrite(pinout.pin_rst, LOW);
-}
-
-void EPD_SPI_interface::send_spi_data(byte data)
-{
- digitalWrite(pinout.pin_cs, LOW);
-
- for (int i = 0; i < 8; i++)
- {
- if ((data & 0x80))
- {
- digitalWrite(pinout.pin_din, HIGH);
- }
- else
- {
- digitalWrite(pinout.pin_din, LOW);
- }
-
- data <<= 1;
- digitalWrite(pinout.pin_clk, HIGH);
- digitalWrite(pinout.pin_clk, LOW);
- }
-
- digitalWrite(pinout.pin_cs, HIGH);
-}
-
-void EPD_SPI_interface::waitfor_busyhigh() const
-{
- while(!(digitalRead(pinout.pin_busy)));
-}
-
-void EPD_SPI_interface::waitfor_busylow() const
-{
- while(digitalRead(pinout.pin_busy));
-}
-
-void EPD_SPI_interface::display(std::unique_ptr<BMP> bmp)
-{
- bmp->seek(0);
- for (uint32_t i{0}; i < EPD_HEIGHT; i++)
- {
- for (uint32_t j{0}; j < EPD_WIDTH / 2; i++) // Every byte is 2 pixels
- {
- send_byte(bmp->read_pixel());
- }
- }
- show();
-}
-
-void EPD_SPI_interface::fill(color c)
-{
- for (uint32_t i{0}; i < EPD_HEIGHT; i++)
- {
- for (uint32_t j{0}; j < EPD_WIDTH / 2; i++) // Every byte is 2 pixels
- {
- send_byte((c << 4)|c);
- }
- }
- show();
-}
\ No newline at end of file
+++ /dev/null
-#ifndef BMP_H_
-#define BMP_H_
-
-/**
- * @file sd_card.h
- * @author Nils Forssén (nilsforssen.se)
- * @brief
- * Useful filesystem functions for interacting with an SD-card
- *
- * @version 0.1
- * @date 2023-12-13
- *
- * @copyright Copyright (c) 2023
- */
-
-#include <SD.h>
-#include <string>
-
-class BMP
-{
-public:
- BMP(const char* filename);
- ~BMP() = default; // Smart pointer closes itself!
-
- byte read_pixel();
- void read_pixel(const char* buffer, const uint32_t len);
- uint32_t position() const;
- uint32_t available() const;
- void seek(const uint32_t pos);
-
-protected:
-private:
- std::unique_ptr<File> bmp_file;
- uint32_t data_idx;
-};
-
-
-#endif
\ No newline at end of file
+++ /dev/null
-#ifndef SIMPLE_SPI_H_
-#define SIMPLE_SPI_H_
-
-#include <Arduino.h>
-#include <memory>
-
-#define EPD_WIDTH 600
-#define EPD_HEIGHT 448
-
-enum color: byte
-{
- EPD_5IN65F_BLACK, /// 000
- EPD_5IN65F_WHITE, /// 001
- EPD_5IN65F_GREEN, /// 010
- EPD_5IN65F_BLUE, /// 011
- EPD_5IN65F_RED, /// 100
- EPD_5IN65F_YELLOW, /// 101
- EPD_5IN65F_ORANGE /// 110
-};
-
-struct epd_spi_pinout
-{
- const uint8_t pin_busy;
- const uint8_t pin_rst;
- const uint8_t pin_dc;
- const uint8_t pin_cs;
- const uint8_t pin_clk;
- const uint8_t pin_din;
-};
-
-class BMP;
-
-class EPD_SPI_interface
-{
-public:
- EPD_SPI_interface(const epd_spi_pinout& p);
- ~EPD_SPI_interface() = default;
-
- void send_byte(byte data);
- void send_command(byte command);
- void display(std::unique_ptr<BMP> bmp);
- void fill(color c);
-
- void show();
- void sleep();
-protected:
-private:
- void send_spi_data(byte data);
- void waitfor_busyhigh() const;
- void waitfor_busylow() const;
-
- const epd_spi_pinout pinout;
-};
-
-#endif
+++ /dev/null
-#ifndef SD_CARD_H_
-#define SD_CARD_H_
-
-/**
- * @file sd_card.h
- * @author Nils Forssén (nilsforssen.se)
- * @brief
- * Useful filesystem functions for interacting with an SD-card
- *
- * @version 0.1
- * @date 2023-12-13
- *
- * @copyright Copyright (c) 2023
- */
-
-#include <memory>
-#include <SD.h>
-
-// #include "SPI.h"
-
-typedef enum
-{
- FS_OK = 0,
- FS_FAIL = 1,
- FS_WARNING = 2,
-} fs_error_t;
-
-namespace SD_card_fs {
-
- fs_error_t create_dir(const char* path);
- fs_error_t remove_dir(const char* path);
- std::unique_ptr<File> get_file(const char* path);
- fs_error_t write_file(const char* path, const char* data); // Make sure data is null-terminated
- fs_error_t append_file(const char* path, const char* data); // Make sure data is null-terminated
- fs_error_t rename_file(const char* path1, const char* path2);
- fs_error_t delete_file(const char* path);
-};
-
-
-#endif
\ No newline at end of file
+++ /dev/null
-#include "include/sd_card.h"
-
-namespace SD_card_fs
-{
- fs_error_t create_dir(const char* path)
- {
- if(SD.mkdir(path)){
- Serial.printf("Created directory: %s\n", path);
- return FS_OK;
- } else {
- Serial.printf("failed to create directory: %s\n", path);
- return FS_FAIL;
- }
- }
-
- fs_error_t remove_dir(const char* path)
- {
- if(SD.rmdir(path)){
- Serial.printf("Removed directory: %s\n", path);
- return FS_OK;
- } else {
- Serial.printf("failed to remove directory: %s\n", path);
- return FS_FAIL;
- }
- }
-
- std::unique_ptr<File> get_file(const char* path)
- {
- File* file = new File{SD.open(path)};
- if(!(*file)){
- Serial.printf("failed to open file: %s\n", path);
- return nullptr;
- }
- return std::unique_ptr<File>{file};
- }
-
- fs_error_t write_file(const char* path, const char* data)
- {
- File file = SD.open(path, FILE_WRITE);
- if(!file){
- Serial.printf("failed to open file: %s\n", path);
- return FS_FAIL;
- }
-
- if(file.print(data)){
- Serial.printf("File written: %s\n", path);
- file.close();
- return FS_OK;
- } else {
- Serial.printf("Write failed: %s\n", path);
- file.close();
- return FS_FAIL;
- }
- }
-
- fs_error_t append_file(const char* path, const char* data){
- File file = SD.open(path, FILE_APPEND);
- if(!file){
- Serial.printf("failed to open file for appending: %s\n", path);
- return FS_FAIL;
- }
-
- if(file.print(data)){
- Serial.printf("Message appended: %s\n", path);
- file.close();
- return FS_OK;
- } else {
- Serial.printf("Append failed: %s\n", path);
- file.close();
- return FS_FAIL;
- }
- }
-
- fs_error_t rename_file(const char* path1, const char* path2){
- if (SD.rename(path1, path2)) {
- Serial.printf("File renamed: %s to %s\n", path1, path2);
- return FS_OK;
- } else {
- Serial.printf("Rename failed: %s to %s\n", path1, path2);
- return FS_FAIL;
- }
- }
-
- fs_error_t delete_file(const char* path){
- if(SD.remove(path)){
- Serial.printf("File deleted: %s\n", path);
- return FS_OK;
- } else {
- Serial.printf("Delete failed: %s\n", path);
- return FS_FAIL;
- }
- }
-}
\ No newline at end of file