Skip to content

Commit

Permalink
Add Colouring Page
Browse files Browse the repository at this point in the history
Still a WIP
  • Loading branch information
candideu committed May 4, 2024
1 parent 536cc80 commit 3f8fdb2
Show file tree
Hide file tree
Showing 8 changed files with 1,371 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Captive portal with (arduino) OTA + LittleFS
// Adapted from https://git.vvvvvvaria.org/then/ESP8266-captive-ota-spiffs

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ESP8266WebServer.h>
#include "./DNSServer.h" // Dns server
// #include <FS.h> // SPIFFS -> deprecated. All instances of SPIFFS have been replaced with LITTLEFS
#include <LittleFS.h> // LittleFS

DNSServer dnsServer;
const byte DNS_PORT = 53;

ESP8266WebServer server(80);

#ifndef STASSID
#define STASSID "A Digital Colouring Book" // ✨ set your network name here ✨
#endif

IPAddress apIP(192, 168, 4, 1);
const char* ssid = STASSID;

void setup() {
Serial.begin(115200);
Serial.println("Booting");

WiFi.mode(WIFI_AP);
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
WiFi.softAP(ssid);
dnsServer.start(DNS_PORT, "*", apIP); // redirect dns request to AP ip

MDNS.begin("esp8266", WiFi.softAPIP());
Serial.println("Ready");
Serial.print("IP address: ");
Serial.println(WiFi.softAPIP());

//File system begin
// SPIFFS.begin(); -> deprecated
LittleFS.begin();


//redirect all traffic to index.html
server.onNotFound([]() {
if(!handleFileRead(server.uri())){
const char *metaRefreshStr = "<head><meta http-equiv=\"refresh\" content=\"0; url=http://192.168.4.1/index.html\" /></head><body><p>redirecting...</p></body>";
server.send(200, "text/html", metaRefreshStr);
}
});

server.begin();

}

void loop() {
dnsServer.processNextRequest();
server.handleClient();
delay(50);
}


String getContentType(String filename){
if(server.hasArg("download")) return "application/octet-stream";
else if(filename.endsWith(".htm")) return "text/html";
else if(filename.endsWith(".html")) return "text/html";
else if(filename.endsWith(".css")) return "text/css";
else if(filename.endsWith(".js")) return "application/javascript";
else if(filename.endsWith(".png")) return "image/png";
else if(filename.endsWith(".gif")) return "image/gif";
else if(filename.endsWith(".jpg")) return "image/jpeg";
else if(filename.endsWith(".ico")) return "image/x-icon";
else if(filename.endsWith(".xml")) return "text/xml";
else if(filename.endsWith(".mp4")) return "video/mp4";
else if(filename.endsWith(".pdf")) return "application/x-pdf";
else if(filename.endsWith(".zip")) return "application/x-zip";
else if(filename.endsWith(".gz")) return "application/x-gzip";
return "text/plain";
}

//Given a file path, look for it in the LittleFS file storage. Returns true if found, returns false if not found.
bool handleFileRead(String path){
if(path.endsWith("/")) path += "index.html";
String contentType = getContentType(path);
String pathWithGz = path + ".gz";
if(LittleFS.exists(pathWithGz) || LittleFS.exists(path)){
if(LittleFS.exists(pathWithGz))
path += ".gz";
File file = LittleFS.open(path, "r");
size_t sent = server.streamFile(file, contentType);
file.close();
return true;
}
return false;
}
166 changes: 166 additions & 0 deletions Example Projects/3-Pocket_Portal--Colouring_Page/DNSServer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
#include "./DNSServer.h"
#include <lwip/def.h>
#include <Arduino.h>

#define DEBUG
#define DEBUG_OUTPUT Serial

DNSServer::DNSServer()
{
_ttl = htonl(60);
_errorReplyCode = DNSReplyCode::NonExistentDomain;
}

bool DNSServer::start(const uint16_t &port, const String &domainName,
const IPAddress &resolvedIP)
{
_port = port;
_domainName = domainName;
_resolvedIP[0] = resolvedIP[0];
_resolvedIP[1] = resolvedIP[1];
_resolvedIP[2] = resolvedIP[2];
_resolvedIP[3] = resolvedIP[3];
downcaseAndRemoveWwwPrefix(_domainName);
return _udp.begin(_port) == 1;
}

void DNSServer::setErrorReplyCode(const DNSReplyCode &replyCode)
{
_errorReplyCode = replyCode;
}

void DNSServer::setTTL(const uint32_t &ttl)
{
_ttl = htonl(ttl);
}

void DNSServer::stop()
{
_udp.stop();
}

void DNSServer::downcaseAndRemoveWwwPrefix(String &domainName)
{
domainName.toLowerCase();
domainName.replace("www.", "");
domainName.replace("https://", "");
}

void DNSServer::processNextRequest()
{
_currentPacketSize = _udp.parsePacket();
if (_currentPacketSize)
{
_buffer = (unsigned char*)malloc(_currentPacketSize * sizeof(char));
_udp.read(_buffer, _currentPacketSize);
_dnsHeader = (DNSHeader*) _buffer;

if (_dnsHeader->QR == DNS_QR_QUERY &&
_dnsHeader->OPCode == DNS_OPCODE_QUERY &&
requestIncludesOnlyOneQuestion() &&
(_domainName == "*" || getDomainNameWithoutWwwPrefix() == _domainName)
)

{
replyWithIP();
}
else if (_dnsHeader->QR == DNS_QR_QUERY)
{
replyWithCustomCode();
}

free(_buffer);
}
}

bool DNSServer::requestIncludesOnlyOneQuestion()
{
return ntohs(_dnsHeader->QDCount) == 1 &&
_dnsHeader->ANCount == 0 &&
_dnsHeader->NSCount == 0 &&
_dnsHeader->ARCount == 0;
}

String DNSServer::getDomainNameWithoutWwwPrefix()
{
String parsedDomainName = "";
unsigned char *start = _buffer + 12;
if (*start == 0)
{
return parsedDomainName;
}
int pos = 0;
while(true)
{
unsigned char labelLength = *(start + pos);
for(int i = 0; i < labelLength; i++)
{
pos++;
parsedDomainName += (char)*(start + pos);
}
pos++;
if (*(start + pos) == 0)
{
downcaseAndRemoveWwwPrefix(parsedDomainName);
return parsedDomainName;
}
else
{
parsedDomainName += ".";
}
}
}

void DNSServer::replyWithIP()
{
_dnsHeader->QR = DNS_QR_RESPONSE;
_dnsHeader->ANCount = _dnsHeader->QDCount;
_dnsHeader->QDCount = _dnsHeader->QDCount;
//_dnsHeader->RA = 1;

_udp.beginPacket(_udp.remoteIP(), _udp.remotePort());
_udp.write(_buffer, _currentPacketSize);

_udp.write((uint8_t)192); // answer name is a pointer
_udp.write((uint8_t)12); // pointer to offset at 0x00c

_udp.write((uint8_t)0); // 0x0001 answer is type A query (host address)
_udp.write((uint8_t)1);

_udp.write((uint8_t)0); //0x0001 answer is class IN (internet address)
_udp.write((uint8_t)1);

_udp.write((unsigned char*)&_ttl, 4);

// Length of RData is 4 bytes (because, in this case, RData is IPv4)
_udp.write((uint8_t)0);
_udp.write((uint8_t)4);
_udp.write(_resolvedIP, sizeof(_resolvedIP));
_udp.endPacket();



#ifdef DEBUG
DEBUG_OUTPUT.print("DNS responds: ");
DEBUG_OUTPUT.print(_resolvedIP[0]);
DEBUG_OUTPUT.print(".");
DEBUG_OUTPUT.print(_resolvedIP[1]);
DEBUG_OUTPUT.print(".");
DEBUG_OUTPUT.print(_resolvedIP[2]);
DEBUG_OUTPUT.print(".");
DEBUG_OUTPUT.print(_resolvedIP[3]);
DEBUG_OUTPUT.print(" for ");
DEBUG_OUTPUT.println(getDomainNameWithoutWwwPrefix());
#endif
}

void DNSServer::replyWithCustomCode()
{
_dnsHeader->QR = DNS_QR_RESPONSE;
_dnsHeader->RCode = (unsigned char)_errorReplyCode;
_dnsHeader->QDCount = 0;

_udp.beginPacket(_udp.remoteIP(), _udp.remotePort());
_udp.write(_buffer, sizeof(DNSHeader));
_udp.endPacket();
}
72 changes: 72 additions & 0 deletions Example Projects/3-Pocket_Portal--Colouring_Page/DNSServer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#ifndef DNSServer_h
#define DNSServer_h
#include <WiFiUdp.h>

#define DNS_QR_QUERY 0
#define DNS_QR_RESPONSE 1
#define DNS_OPCODE_QUERY 0

enum class DNSReplyCode
{
NoError = 0,
FormError = 1,
ServerFailure = 2,
NonExistentDomain = 3,
NotImplemented = 4,
Refused = 5,
YXDomain = 6,
YXRRSet = 7,
NXRRSet = 8
};

struct DNSHeader
{
uint16_t ID; // identification number
unsigned char RD : 1; // recursion desired
unsigned char TC : 1; // truncated message
unsigned char AA : 1; // authoritive answer
unsigned char OPCode : 4; // message_type
unsigned char QR : 1; // query/response flag
unsigned char RCode : 4; // response code
unsigned char Z : 3; // its z! reserved
unsigned char RA : 1; // recursion available
uint16_t QDCount; // number of question entries
uint16_t ANCount; // number of answer entries
uint16_t NSCount; // number of authority entries
uint16_t ARCount; // number of resource entries
};

class DNSServer
{
public:
DNSServer();
void processNextRequest();
void setErrorReplyCode(const DNSReplyCode &replyCode);
void setTTL(const uint32_t &ttl);

// Returns true if successful, false if there are no sockets available
bool start(const uint16_t &port,
const String &domainName,
const IPAddress &resolvedIP);
// stops the DNS server
void stop();

private:
WiFiUDP _udp;
uint16_t _port;
String _domainName;
unsigned char _resolvedIP[4];
int _currentPacketSize;
unsigned char* _buffer;
DNSHeader* _dnsHeader;
uint32_t _ttl;
DNSReplyCode _errorReplyCode;

void downcaseAndRemoveWwwPrefix(String &domainName);
String getDomainNameWithoutWwwPrefix();
bool requestIncludesOnlyOneQuestion();
void replyWithIP();
void replyWithCustomCode();
};
#endif

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
var svgID = "colour"; // 🔄 Replace 'colour' with the HTML ID of the illustration's SVG (if you want everything to be colour-able), or the ID of the layer or group to colour in

//// ✨ COLOUR PICKER CUSTOM COLOR ✨ ////

// Function to handle color picker selection
function handleColorPickerSelection(event) {
// Check if a color picker input element already exists
if (!document.getElementById("colorPickerInput")) {
// Create a color picker input element
var input = document.createElement("input");
input.type = "color";
input.id = "colorPickerInput"; // Assign an id to the input element

// Add event listener for color change
input.addEventListener("change", function () {
// Set the current fill to the selected color
var selectedColor = input.value;
_currentFill = "fill:" + selectedColor;

// Remove the input element from the body
document.body.removeChild(input);
});

// Append the input element to the body
document.body.appendChild(input);

// Trigger click event on color picker input
input.click();
}
}

// Add event listener to the custom swatch for color picker
document
.getElementById("colorPickerSwatch")
.addEventListener("click", handleColorPickerSelection);

//// ✨ CHANGING THE FILL COLOUR DYNAMICALLY ✨ ////

// default selected colour when page is loaded
var _currentFill = "fill:#fff";

// change the fill colour when clicking inside of your ilustration
$("#" + svgID).click(function (event) {
$(event.target).attr("style", _currentFill);
});
var $swatches = $("#swatches");
$swatches.click(function (event) {
$swatch = $(event.target);
loc = [parseInt($swatch.attr("x"), 10), parseInt($swatch.attr("y"), 10)];
$("#selection", $swatches).attr("x", loc[0]);
$("#selection", $swatches).attr("y", loc[1]);
_currentFill = $swatch.attr("style");
});
Loading

0 comments on commit 3f8fdb2

Please sign in to comment.